support tor connection to monero network through monero-java

cleanup startup routine for stability
remove call to `get_connections`
increase wallet startup timeout to 1 hour
increase app startup timeout to 5 minutes
skip checkstyle in make commands
This commit is contained in:
woodser 2023-04-02 17:11:01 -04:00
parent 8305c62510
commit fd69f4250b
10 changed files with 149 additions and 88 deletions

View File

@ -16,11 +16,14 @@ haveno:
# build haveno without tests
skip-tests: localnet
./gradlew build -x test
./gradlew build -x test -x checkstyleMain -x checkstyleTest
# quick build desktop and daemon apps without tests
haveno-apps:
./gradlew :core:compileJava :desktop:build -x test
./gradlew :core:compileJava :desktop:build -x test -x checkstyleMain -x checkstyleTest
refresh-deps:
./gradlew --write-verification-metadata sha256 && ./gradlew build --refresh-keys --refresh-dependencies -x test -x checkstyleMain -x checkstyleTest
deploy:
# create a new screen session named 'localnet'
@ -84,6 +87,17 @@ monerod-local2:
--rpc-access-control-origins http://localhost:8080 \
--fixed-difficulty 300
funding-wallet-stagenet:
./.localnet/monero-wallet-rpc \
--rpc-bind-port 18084 \
--rpc-login rpc_user:abc123 \
--rpc-access-control-origins http://localhost:8080 \
--wallet-dir ./.localnet \
--daemon-ssl-allow-any-cert \
--daemon-address http://127.0.0.1:38081
#--proxy 127.0.0.1:49775 \
funding-wallet-local:
./.localnet/monero-wallet-rpc \
--testnet \
@ -214,10 +228,11 @@ arbitrator-daemon-stagenet:
--appName=haveno-XMR_STAGENET_arbitrator \
--apiPassword=apitest \
--apiPort=9998 \
--passwordRequired=false
--passwordRequired=false \
--xmrNode=http://127.0.0.1:38081
# Arbitrator needs to be registered before making trades
arbitrator-desktop-stagenet:
# Arbitrator needs to be registered before making trades
./haveno-desktop$(APP_EXT) \
--baseCurrencyNetwork=XMR_STAGENET \
--useLocalhostForP2P=false \
@ -225,7 +240,8 @@ arbitrator-desktop-stagenet:
--nodePort=4444 \
--appName=haveno-XMR_STAGENET_arbitrator \
--apiPassword=apitest \
--apiPort=9998
--apiPort=9998 \
--xmrNode=http://127.0.0.1:38081
user1-daemon-stagenet:
./haveno-daemon$(APP_EXT) \

View File

@ -1,5 +1,6 @@
package haveno.core.api;
import haveno.common.UserThread;
import haveno.common.app.DevEnv;
import haveno.common.config.BaseCurrencyNetwork;
import haveno.common.config.Config;
@ -7,6 +8,7 @@ import haveno.core.trade.HavenoUtils;
import haveno.core.xmr.model.EncryptedConnectionList;
import haveno.core.xmr.setup.DownloadListener;
import haveno.core.xmr.setup.WalletsSetup;
import haveno.network.Socks5ProxyProvider;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.LongProperty;
import javafx.beans.property.ObjectProperty;
@ -20,7 +22,6 @@ import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import monero.common.MoneroConnectionManager;
import monero.common.MoneroConnectionManagerListener;
import monero.common.MoneroError;
import monero.common.MoneroRpcConnection;
import monero.common.TaskLooper;
import monero.daemon.MoneroDaemonRpc;
@ -34,6 +35,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@Slf4j
@ -60,7 +62,7 @@ public final class CoreMoneroConnectionsService {
new MoneroRpcConnection("http://stagenet.melo.tools:38081").setPriority(2),
new MoneroRpcConnection("http://node.sethforprivacy.com:38089").setPriority(2),
new MoneroRpcConnection("http://node2.sethforprivacy.com:38089").setPriority(2),
new MoneroRpcConnection("http://ct36dsbe3oubpbebpxmiqz4uqk6zb6nhmkhoekileo4fts23rvuse2qd.onion:38081").setPriority(2)
new MoneroRpcConnection("http://plowsof3t5hogddwabaeiyrno25efmzfxyro2vligremt7sxpsclfaid.onion:38089").setPriority(2)
));
DEFAULT_CONNECTIONS.put(BaseCurrencyNetwork.XMR_MAINNET, Arrays.asList(
new MoneroRpcConnection("http://127.0.0.1:18081").setPriority(1),
@ -84,6 +86,7 @@ public final class CoreMoneroConnectionsService {
private final IntegerProperty numPeers = new SimpleIntegerProperty(0);
private final LongProperty chainHeight = new SimpleLongProperty(0);
private final DownloadListener downloadListener = new DownloadListener();
private Socks5ProxyProvider socks5ProxyProvider;
private MoneroDaemonRpc daemon;
@Getter
@ -98,16 +101,15 @@ public final class CoreMoneroConnectionsService {
CoreAccountService accountService,
CoreMoneroNodeService nodeService,
MoneroConnectionManager connectionManager,
EncryptedConnectionList connectionList) {
EncryptedConnectionList connectionList,
Socks5ProxyProvider socks5ProxyProvider) {
this.config = config;
this.coreContext = coreContext;
this.accountService = accountService;
this.nodeService = nodeService;
this.connectionManager = connectionManager;
this.connectionList = connectionList;
// initialize immediately if monerod configured
if (!"".equals(config.xmrNode)) initialize();
this.socks5ProxyProvider = socks5ProxyProvider;
// initialize after account open and basic setup
walletsSetup.addSetupTaskHandler(() -> { // TODO: use something better than legacy WalletSetup for notification to initialize
@ -145,6 +147,10 @@ public final class CoreMoneroConnectionsService {
return this.daemon;
}
public String getProxyUri() {
return socks5ProxyProvider.getSocks5Proxy() == null ? null : socks5ProxyProvider.getSocks5Proxy().getInetAddress().getHostAddress() + ":" + socks5ProxyProvider.getSocks5Proxy().getPort();
}
public void addListener(MoneroConnectionManagerListener listener) {
synchronized (lock) {
connectionManager.addListener(listener);
@ -319,30 +325,41 @@ public final class CoreMoneroConnectionsService {
private void initialize() {
synchronized (lock) {
// reset connection manager's connections and listeners
// reset connection manager
connectionManager.reset();
connectionManager.setTimeout(REFRESH_PERIOD_REMOTE_MS);
// load connections
connectionList.getConnections().forEach(connectionManager::addConnection);
log.info("TOR proxy URI: " + getProxyUri());
for (MoneroRpcConnection connection : connectionList.getConnections()) {
if (connection.isOnion()) connection.setProxyUri(getProxyUri());
connectionManager.addConnection(connection);
}
log.info("Read " + connectionList.getConnections().size() + " connections from disk");
// add default connections
for (MoneroRpcConnection connection : DEFAULT_CONNECTIONS.get(Config.baseCurrencyNetwork())) {
if (connectionList.hasConnection(connection.getUri())) continue;
if (connection.isOnion()) connection.setProxyUri(getProxyUri());
addConnection(connection);
}
// restore last used connection if present
var currentConnectionUri = connectionList.getCurrentConnectionUri();
// restore last used connection if unconfigured and present
Optional<String> currentConnectionUri = null;
if ("".equals(config.xmrNode)) {
currentConnectionUri = connectionList.getCurrentConnectionUri();
if (currentConnectionUri.isPresent()) connectionManager.setConnection(currentConnectionUri.get());
} else if (!isInitialized) {
// set monero connection from startup arguments
if (!isInitialized && !"".equals(config.xmrNode)) {
connectionManager.setConnection(new MoneroRpcConnection(config.xmrNode, config.xmrNodeUsername, config.xmrNodePassword).setPriority(1));
MoneroRpcConnection connection = new MoneroRpcConnection(config.xmrNode, config.xmrNodeUsername, config.xmrNodePassword).setPriority(1);
if (connection.isOnion()) connection.setProxyUri(getProxyUri());
connectionManager.setConnection(connection);
currentConnectionUri = Optional.of(connection.getUri());
}
// restore configuration
connectionManager.setAutoSwitch(connectionList.getAutoSwitch());
// restore configuration and check connection
if ("".equals(config.xmrNode)) connectionManager.setAutoSwitch(connectionList.getAutoSwitch());
long refreshPeriod = connectionList.getRefreshPeriod();
if (refreshPeriod > 0) connectionManager.startCheckingConnection(refreshPeriod);
else if (refreshPeriod == 0) connectionManager.startCheckingConnection();
@ -351,9 +368,6 @@ public final class CoreMoneroConnectionsService {
// run once
if (!isInitialized) {
// register connection change listener
connectionManager.addListener(this::onConnectionChanged);
// register local node listener
nodeService.addListener(new MoneroNodeServiceListener() {
@Override
@ -369,8 +383,6 @@ public final class CoreMoneroConnectionsService {
checkConnection();
}
});
isInitialized = true;
}
// if offline and last connection is local, start local node if offline
@ -385,7 +397,7 @@ public final class CoreMoneroConnectionsService {
});
// prefer to connect to local node unless prevented by configuration
if (("".equals(config.xmrNode) || HavenoUtils.isLocalHost(config.xmrNode)) &&
if ("".equals(config.xmrNode) &&
(!connectionManager.isConnected() || connectionManager.getAutoSwitch()) &&
nodeService.isConnected()) {
MoneroRpcConnection connection = connectionManager.getConnectionByUri(nodeService.getDaemon().getRpcConnection().getUri());
@ -401,12 +413,19 @@ public final class CoreMoneroConnectionsService {
connectionManager.setConnection(connectionManager.getBestAvailableConnection());
}
// update connection
// register connection change listener
if (!isInitialized) {
connectionManager.addListener(this::onConnectionChanged);
isInitialized = true;
}
// announce connection
onConnectionChanged(connectionManager.getConnection());
}
}
private void onConnectionChanged(MoneroRpcConnection currentConnection) {
// TODO: ignore if shutdown
synchronized (lock) {
if (currentConnection == null) {
daemon = null;
@ -422,13 +441,18 @@ public final class CoreMoneroConnectionsService {
}
private void startPollingDaemon() {
synchronized (lock) {
updateDaemonInfo();
if (updateDaemonLooper != null) updateDaemonLooper.stop();
updateDaemonInfo();
updateDaemonLooper = new TaskLooper(() -> {
updateDaemonInfo();
});
UserThread.runAfter(() -> {
synchronized (lock) {
if (updateDaemonLooper != null) updateDaemonLooper.stop();
updateDaemonLooper = new TaskLooper(() -> updateDaemonInfo());
updateDaemonLooper.start(getDefaultRefreshPeriodMs());
}
}, getDefaultRefreshPeriodMs() / 1000);
}
}
private void updateDaemonInfo() {
try {
@ -438,12 +462,18 @@ public final class CoreMoneroConnectionsService {
//System.out.println(JsonUtils.serialize(lastInfo));
//System.out.println(JsonUtils.serialize(daemon.getSyncInfo()));
chainHeight.set(lastInfo.getTargetHeight() == 0 ? lastInfo.getHeight() : lastInfo.getTargetHeight());
try {
peers.set(getOnlinePeers());
} catch (MoneroError err) {
peers.set(new ArrayList<MoneroPeer>()); // TODO: peers unknown due to restricted RPC call
}
numPeers.set(peers.get().size());
// set peer connections
// TODO: peers often uknown due to restricted RPC call, skipping call to get peer connections
// try {
// peers.set(getOnlinePeers());
// } catch (Exception err) {
// // TODO: peers unknown due to restricted RPC call
// }
// numPeers.set(peers.get().size());
numPeers.set(lastInfo.getNumOutgoingConnections() + lastInfo.getNumIncomingConnections());
peers.set(new ArrayList<MoneroPeer>());
if (lastErrorTimestamp != null) {
log.info("Successfully fetched daemon info after previous error");
lastErrorTimestamp = null;

View File

@ -17,11 +17,9 @@
package haveno.core.app;
import ch.qos.logback.classic.Level;
import haveno.common.Timer;
import haveno.common.UserThread;
import haveno.common.app.DevEnv;
import haveno.common.app.Log;
import haveno.common.app.Version;
import haveno.common.config.BaseCurrencyNetwork;
import haveno.common.config.Config;
@ -102,7 +100,7 @@ public class HavenoSetup {
private static final String VERSION_FILE_NAME = "version";
private static final String RESYNC_SPV_FILE_NAME = "resyncSpv";
private static final long STARTUP_TIMEOUT_MINUTES = 4;
private static final long STARTUP_TIMEOUT_MINUTES = 5;
private final DomainInitialisation domainInitialisation;
private final P2PNetworkSetup p2PNetworkSetup;
@ -403,9 +401,9 @@ public class HavenoSetup {
if (displayTorNetworkSettingsHandler != null)
displayTorNetworkSettingsHandler.accept(true);
log.info("Set log level for org.berndpruenster.netlayer classes to DEBUG to show more details for " +
"Tor network connection issues");
Log.setCustomLogLevel("org.berndpruenster.netlayer", Level.DEBUG);
// log.info("Set log level for org.berndpruenster.netlayer classes to DEBUG to show more details for " +
// "Tor network connection issues");
// Log.setCustomLogLevel("org.berndpruenster.netlayer", Level.DEBUG);
}, STARTUP_TIMEOUT_MINUTES, TimeUnit.MINUTES);
@ -444,7 +442,7 @@ public class HavenoSetup {
checkForInvalidMakerFeeTxs();
}
},
() -> walletInitialized.set(true));
() -> {});
}
private void initDomainServices() {

View File

@ -142,12 +142,12 @@ public class P2PNetworkSetup {
bootstrapState.set(Res.get("mainView.bootstrapState.torNodeCreated"));
p2PNetworkIconId.set("image-connection-tor");
// invoke handler to initialize wallet
initWalletServiceHandler.run();
// We want to get early connected to the price relay so we call it already now
priceFeedService.setCurrencyCodeOnInit();
priceFeedService.requestPrices();
// invoke handler to initialize wallet
initWalletServiceHandler.run();
}
@Override

View File

@ -424,7 +424,7 @@ public class HavenoUtils {
public static boolean isLocalHost(String uri) {
try {
String host = new URI(uri).getHost();
return host.equals(LOOPBACK_HOST) || host.equals(LOCALHOST);
return LOOPBACK_HOST.equals(host) || LOCALHOST.equals(host);
} catch (Exception e) {
throw new RuntimeException(e);
}

View File

@ -1662,13 +1662,17 @@ public abstract class Trade implements Tradable, Model {
private void setWalletRefreshPeriod(long walletRefreshPeriod) {
if (this.isShutDown) return;
if (this.walletRefreshPeriod != null && this.walletRefreshPeriod == walletRefreshPeriod) return;
log.info("Setting wallet refresh rate for {} {} to {}", getClass().getSimpleName(), getId(), walletRefreshPeriod);
this.walletRefreshPeriod = walletRefreshPeriod;
synchronized (walletLock) {
if (getWallet() != null) {
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 (txPollLooper != null) {
txPollLooper.stop();
txPollLooper = null;
}
}
startPolling();
}

View File

@ -349,7 +349,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
.collect(Collectors.toSet());
unreservedFrozenKeyImages.removeAll(reservedKeyImages);
if (!unreservedFrozenKeyImages.isEmpty()) {
log.info("Thawing outputs which are not reserved for offer or trade: " + unreservedFrozenKeyImages);
log.warn("Thawing outputs which are not reserved for offer or trade: " + unreservedFrozenKeyImages);
xmrWalletService.thawOutputs(unreservedFrozenKeyImages);
xmrWalletService.saveMainWallet();
}

View File

@ -92,7 +92,7 @@ public class WalletsSetup {
@Getter
public final BooleanProperty walletsSetupFailed = new SimpleBooleanProperty();
private static final long STARTUP_TIMEOUT = 180;
private static final long STARTUP_TIMEOUT_SECONDS = 3600; // 1 hour
private static final String SPV_CHAIN_FILE_NAME = "haveno.spvchain";
private final RegTestHost regTestHost;
@ -167,7 +167,7 @@ public class WalletsSetup {
Timer timeoutTimer = UserThread.runAfter(() ->
exceptionHandler.handleException(new TimeoutException("Wallet did not initialize in " +
STARTUP_TIMEOUT + " seconds.")), STARTUP_TIMEOUT);
STARTUP_TIMEOUT_SECONDS + " seconds.")), STARTUP_TIMEOUT_SECONDS);
backupWallets();

View File

@ -211,7 +211,7 @@ public class XmrWalletService {
public MoneroWalletRpc createWallet(String walletName) {
log.info("{}.createWallet({})", getClass().getSimpleName(), walletName);
if (isShutDown) throw new IllegalStateException("Cannot create wallet because shutting down");
return createWallet(new MoneroWalletConfig()
return createWalletRpc(new MoneroWalletConfig()
.setPath(walletName)
.setPassword(getWalletPassword()),
null);
@ -220,7 +220,7 @@ public class XmrWalletService {
public MoneroWalletRpc openWallet(String walletName) {
log.info("{}.openWallet({})", getClass().getSimpleName(), walletName);
if (isShutDown) throw new IllegalStateException("Cannot open wallet because shutting down");
return openWallet(new MoneroWalletConfig()
return openWalletRpc(new MoneroWalletConfig()
.setPath(walletName)
.setPassword(getWalletPassword()),
null);
@ -546,51 +546,49 @@ public class XmrWalletService {
private void maybeInitMainWallet() {
if (wallet != null) throw new RuntimeException("Main wallet is already initialized");
MoneroDaemonRpc daemon = connectionsService.getDaemon();
log.info("Initializing main wallet with " + (daemon == null ? "daemon: null" : "monerod uri=" + daemon.getRpcConnection().getUri() + ", height=" + connectionsService.getLastInfo().getHeight()));
// open or create wallet
MoneroWalletConfig walletConfig = new MoneroWalletConfig().setPath(MONERO_WALLET_NAME).setPassword(getWalletPassword());
if (MoneroUtils.walletExists(xmrWalletFile.getPath())) {
wallet = openWallet(walletConfig, rpcBindPort);
wallet = openWalletRpc(walletConfig, rpcBindPort);
} else if (connectionsService.getConnection() != null && Boolean.TRUE.equals(connectionsService.getConnection().isConnected())) {
wallet = createWallet(walletConfig, rpcBindPort);
wallet = createWalletRpc(walletConfig, rpcBindPort);
}
// wallet is not initialized until connected to a daemon
// handle when wallet initialized and synced
if (wallet != null) {
if (connectionsService.getDaemon() == null) System.out.println("Daemon: null");
else {
System.out.println("Daemon uri: " + connectionsService.getDaemon().getRpcConnection().getUri());
System.out.println("Daemon height: " + connectionsService.getDaemon().getInfo().getHeight());
}
System.out.println("Monero wallet uri: " + wallet.getRpcConnection().getUri());
System.out.println("Monero wallet path: " + wallet.getPath());
System.out.println("Monero wallet primary address: " + wallet.getPrimaryAddress());
// sync wallet which updates app startup state
log.info("Monero wallet uri={}, path={}", wallet.getRpcConnection().getUri(), wallet.getPath());
try {
// sync main wallet
log.info("Syncing main wallet");
long time = System.currentTimeMillis();
wallet.sync(); // blocking
log.info("Done syncing main wallet in " + (System.currentTimeMillis() - time) + " ms");
wallet.startSyncing(connectionsService.getDefaultRefreshPeriodMs());
connectionsService.doneDownload(); // TODO: using this to signify both daemon and wallet synced, refactor sync handling of both
saveMainWallet(false); // skip backup on open
if (getMoneroNetworkType() != MoneroNetworkType.MAINNET) log.info("Monero wallet balance={}, unlocked balance={}", wallet.getBalance(0), wallet.getUnlockedBalance(0));
// TODO: using this to signify both daemon and wallet synced, refactor sync handling of both
connectionsService.doneDownload();
// save but skip backup on initialization
saveMainWallet(false);
} catch (Exception e) {
log.warn("Error syncing main wallet: {}", e.getMessage());
}
System.out.println("Monero wallet balance: " + wallet.getBalance(0));
System.out.println("Monero wallet unlocked balance: " + wallet.getUnlockedBalance(0));
// notify setup that main wallet is initialized
havenoSetup.getWalletInitialized().set(true); // TODO: change to listener pattern?
// TODO: move to try..catch? refactor startup to call this and sync off main thread?
havenoSetup.getWalletInitialized().set(true); // TODO: change to listener pattern
// register internal listener to notify external listeners
wallet.addListener(new XmrWalletListener());
}
}
private MoneroWalletRpc createWallet(MoneroWalletConfig config, Integer port) {
private MoneroWalletRpc createWalletRpc(MoneroWalletConfig config, Integer port) {
// must be connected to daemon
MoneroRpcConnection connection = connectionsService.getConnection();
@ -602,10 +600,15 @@ public class XmrWalletService {
// create wallet
try {
log.info("Creating wallet " + config.getPath());
// prevent wallet rpc from syncing
walletRpc.stopSyncing();
// create wallet
log.info("Creating wallet " + config.getPath() + " connected to daemon " + connection.getUri());
long time = System.currentTimeMillis();
walletRpc.createWallet(config);
log.info("Done creating wallet " + walletRpc.getPath() + " in " + (System.currentTimeMillis() - time) + " ms");
log.info("Done creating wallet " + config.getPath() + " in " + (System.currentTimeMillis() - time) + " ms");
return walletRpc;
} catch (Exception e) {
e.printStackTrace();
@ -614,17 +617,21 @@ public class XmrWalletService {
}
}
private MoneroWalletRpc openWallet(MoneroWalletConfig config, Integer port) {
private MoneroWalletRpc openWalletRpc(MoneroWalletConfig config, Integer port) {
// start monero-wallet-rpc instance
MoneroWalletRpc walletRpc = startWalletRpcInstance(port);
// open wallet
try {
// prevent wallet rpc from syncing
walletRpc.stopSyncing();
// open wallet
log.info("Opening wallet " + config.getPath());
walletRpc.openWallet(config);
walletRpc.setDaemonConnection(connectionsService.getConnection());
log.info("Done opening wallet " + walletRpc.getPath());
walletRpc.openWallet(config.setServer(connectionsService.getConnection()));
log.info("Done opening wallet " + config.getPath());
return walletRpc;
} catch (Exception e) {
e.printStackTrace();
@ -655,6 +662,10 @@ public class XmrWalletService {
if (connection != null) {
cmd.add("--daemon-address");
cmd.add(connection.getUri());
if (connection.isOnion() && connection.getProxyUri() != null) {
cmd.add("--proxy");
cmd.add(connection.getProxyUri());
}
if (connection.getUsername() != null) {
cmd.add("--daemon-login");
cmd.add(connection.getUsername() + ":" + connection.getPassword());
@ -669,7 +680,9 @@ public class XmrWalletService {
return MONERO_WALLET_RPC_MANAGER.startInstance(cmd);
}
// TODO: monero-wallet-rpc needs restarted if applying tor proxy
private void setDaemonConnection(MoneroRpcConnection connection) {
if (isShutDown) return;
log.info("Setting wallet daemon connection: " + (connection == null ? null : connection.getUri()));
if (wallet == null) maybeInitMainWallet();
else {

View File

@ -117,7 +117,7 @@ public class HavenoApp extends Application implements UncaughtExceptionHandler {
}
public void startApplication(Runnable onApplicationStartedHandler) {
log.info("Running startApplication...");
log.info("Starting application");
try {
mainView = loadMainView(injector);
mainView.setOnApplicationStartedHandler(onApplicationStartedHandler);