diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index e2250dba..be52dae3 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -1633,6 +1633,12 @@ public class AppController implements Initializable { statusUpdated(new StatusEvent(event.getStatus())); } + @Subscribe + public void torExternalStatus(TorExternalStatusEvent event) { + serverToggle.setDisable(false); + statusUpdated(new StatusEvent(event.getStatus())); + } + @Subscribe public void newBlock(NewBlockEvent event) { setServerToggleTooltip(event.getHeight()); diff --git a/src/main/java/com/sparrowwallet/sparrow/AppServices.java b/src/main/java/com/sparrowwallet/sparrow/AppServices.java index 89c8f926..75d4ddf2 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppServices.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppServices.java @@ -94,7 +94,7 @@ public class AppServices { @Override public void changed(ObservableValue observable, Boolean oldValue, Boolean online) { if(online) { - if(Config.get().requiresTor() && !isTorRunning()) { + if(Config.get().requiresInternalTor() && !isTorRunning()) { torService.start(); } else { restartServices(); @@ -122,7 +122,7 @@ public class AppServices { onlineProperty.addListener(onlineServicesListener); if(config.getMode() == Mode.ONLINE) { - if(config.requiresTor()) { + if(config.requiresInternalTor()) { torService.start(); } else { restartServices(); @@ -233,6 +233,12 @@ public class AppServices { } } + if(failEvent.getSource().getException() instanceof ProxyServerException && Config.get().isUseProxy() && Config.get().requiresTor()) { + Config.get().setUseProxy(false); + Platform.runLater(() -> restartService(torService)); + return; + } + onlineProperty.removeListener(onlineServicesListener); onlineProperty.setValue(false); onlineProperty.addListener(onlineServicesListener); @@ -314,6 +320,23 @@ public class AppServices { EventManager.get().post(new TorReadyStatusEvent()); }); torService.setOnFailed(workerStateEvent -> { + Throwable exception = workerStateEvent.getSource().getException(); + if(exception instanceof TorServerAlreadyBoundException) { + String proxyServer = Config.get().getProxyServer(); + if(proxyServer == null || proxyServer.equals("")) { + proxyServer = "localhost:9050"; + Config.get().setProxyServer(proxyServer); + } + + if(proxyServer.equals("localhost:9050") || proxyServer.equals("127.0.0.1:9050")) { + Config.get().setUseProxy(true); + torService.cancel(); + restartServices(); + EventManager.get().post(new TorExternalStatusEvent()); + return; + } + } + EventManager.get().post(new TorFailedStatusEvent(workerStateEvent.getSource().getException())); }); diff --git a/src/main/java/com/sparrowwallet/sparrow/event/TorExternalStatusEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/TorExternalStatusEvent.java new file mode 100644 index 00000000..9c6cf57a --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/event/TorExternalStatusEvent.java @@ -0,0 +1,7 @@ +package com.sparrowwallet.sparrow.event; + +public class TorExternalStatusEvent extends TorStatusEvent { + public TorExternalStatusEvent() { + super("Tor is already running, using external instance..."); + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/event/TorFailedStatusEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/TorFailedStatusEvent.java index e8192804..1ba1c899 100644 --- a/src/main/java/com/sparrowwallet/sparrow/event/TorFailedStatusEvent.java +++ b/src/main/java/com/sparrowwallet/sparrow/event/TorFailedStatusEvent.java @@ -1,12 +1,13 @@ package com.sparrowwallet.sparrow.event; +import com.sparrowwallet.sparrow.net.TorServerAlreadyBoundException; + public class TorFailedStatusEvent extends TorStatusEvent { private final Throwable exception; public TorFailedStatusEvent(Throwable exception) { - super("Tor failed to start: " + (exception.getCause() != null ? - (exception.getCause().getMessage().contains("Failed to bind") ? exception.getCause().getMessage() + " Is a Tor proxy already running?" : exception.getCause().getMessage() ) : - exception.getMessage())); + super("Tor failed to start: " + (exception instanceof TorServerAlreadyBoundException ? exception.getCause().getMessage() + " Is a Tor proxy already running?" : + (exception.getCause() != null ? exception.getCause().getMessage() : exception.getMessage()))); this.exception = exception; } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Config.java b/src/main/java/com/sparrowwallet/sparrow/io/Config.java index b4f7a52d..d5a958f1 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Config.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Config.java @@ -303,8 +303,16 @@ public class Config { return getServerType() == ServerType.BITCOIN_CORE ? getCoreServer() : (getServerType() == ServerType.PUBLIC_ELECTRUM_SERVER ? getPublicElectrumServer() : getElectrumServer()); } + public boolean requiresInternalTor() { + if(isUseProxy()) { + return false; + } + + return requiresTor(); + } + public boolean requiresTor() { - if(isUseProxy() || !hasServerAddress()) { + if(!hasServerAddress()) { return false; } diff --git a/src/main/java/com/sparrowwallet/sparrow/net/ProxyServerException.java b/src/main/java/com/sparrowwallet/sparrow/net/ProxyServerException.java new file mode 100644 index 00000000..eebce90b --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/net/ProxyServerException.java @@ -0,0 +1,7 @@ +package com.sparrowwallet.sparrow.net; + +public class ProxyServerException extends ServerException { + public ProxyServerException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/net/TcpTransport.java b/src/main/java/com/sparrowwallet/sparrow/net/TcpTransport.java index af69fcf0..c04eebdf 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/TcpTransport.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/TcpTransport.java @@ -196,6 +196,10 @@ public class TcpTransport implements Transport, Closeable { } catch(SSLHandshakeException e) { throw new TlsServerException(server, e); } catch(IOException e) { + if(e.getStackTrace().length > 0 && e.getStackTrace()[0].getClassName().contains("SocksSocketImpl")) { + throw new ProxyServerException(e); + } + throw new ServerException(e); } } diff --git a/src/main/java/com/sparrowwallet/sparrow/net/TorServerAlreadyBoundException.java b/src/main/java/com/sparrowwallet/sparrow/net/TorServerAlreadyBoundException.java new file mode 100644 index 00000000..2ee06fab --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/net/TorServerAlreadyBoundException.java @@ -0,0 +1,11 @@ +package com.sparrowwallet.sparrow.net; + +public class TorServerAlreadyBoundException extends TorServerException { + public TorServerAlreadyBoundException(Throwable cause) { + super(cause); + } + + public TorServerAlreadyBoundException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/net/TorServerException.java b/src/main/java/com/sparrowwallet/sparrow/net/TorServerException.java new file mode 100644 index 00000000..4919b217 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/net/TorServerException.java @@ -0,0 +1,11 @@ +package com.sparrowwallet.sparrow.net; + +public class TorServerException extends ServerException { + public TorServerException(Throwable cause) { + super(cause); + } + + public TorServerException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/net/TorService.java b/src/main/java/com/sparrowwallet/sparrow/net/TorService.java index c59cedb6..764d7eff 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/TorService.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/TorService.java @@ -32,7 +32,7 @@ public class TorService extends ScheduledService { @Override protected Task createTask() { return new Task<>() { - protected NativeTor call() throws IOException { + protected NativeTor call() throws IOException, TorServerException { if(Tor.getDefault() == null) { Path path = Files.createTempDirectory(TOR_DIR_PREFIX); File torInstallDir = path.toFile(); @@ -45,11 +45,15 @@ public class TorService extends ScheduledService { return new NativeTor(torInstallDir, Collections.emptyList(), override); } catch(TorCtlException e) { - log.error("Failed to start Tor", e); if(e.getCause() instanceof TorControlError) { - throw new IOException("Failed to start Tor", e.getCause()); + if(e.getCause().getMessage().contains("Failed to bind")) { + throw new TorServerAlreadyBoundException("Tor server already bound", e.getCause()); + } + log.error("Failed to start Tor", e); + throw new TorServerException("Failed to start Tor", e.getCause()); } else { - throw new IOException("Failed to start Tor", e); + log.error("Failed to start Tor", e); + throw new TorServerException("Failed to start Tor", e); } } } diff --git a/src/main/java/com/sparrowwallet/sparrow/preferences/ServerPreferencesController.java b/src/main/java/com/sparrowwallet/sparrow/preferences/ServerPreferencesController.java index 55ce1873..967d34c0 100644 --- a/src/main/java/com/sparrowwallet/sparrow/preferences/ServerPreferencesController.java +++ b/src/main/java/com/sparrowwallet/sparrow/preferences/ServerPreferencesController.java @@ -306,7 +306,7 @@ public class ServerPreferencesController extends PreferencesDetailController { testConnection.setGraphic(getGlyph(FontAwesome5.Glyph.ELLIPSIS_H, null)); testResults.setText("Connecting to " + config.getServerAddress() + "..."); - if(Config.get().requiresTor() && Tor.getDefault() == null) { + if(Config.get().requiresInternalTor() && Tor.getDefault() == null) { startTor(); } else { startElectrumConnection(); @@ -430,8 +430,10 @@ public class ServerPreferencesController extends PreferencesDetailController { Throwable exception = workerStateEvent.getSource().getException(); if(Config.get().getServerType() == ServerType.ELECTRUM_SERVER && - exception.getCause() != null && exception.getCause() instanceof TorControlError && exception.getCause().getMessage().contains("Failed to bind") && - useProxyOriginal == null && !useProxy.isSelected() && proxyHost.getText().isEmpty() && proxyPort.getText().isEmpty()) { + exception instanceof TorServerAlreadyBoundException && + useProxyOriginal == null && !useProxy.isSelected() && + (proxyHost.getText().isEmpty() || proxyHost.getText().equals("localhost") || proxyHost.getText().equals("127.0.0.1")) && + (proxyPort.getText().isEmpty() || proxyPort.getText().equals("9050"))) { useProxy.setSelected(true); proxyHost.setText("localhost"); proxyPort.setText("9050"); @@ -566,7 +568,9 @@ public class ServerPreferencesController extends PreferencesDetailController { } reason = tlsServerException.getMessage() + "\n\n" + reason; - } else if(exception.getCause() != null && exception.getCause() instanceof TorControlError && exception.getCause().getMessage().contains("Failed to bind")) { + } else if(exception instanceof ProxyServerException) { + reason += ". Check if the proxy server is running."; + } else if(exception instanceof TorServerAlreadyBoundException) { reason += "\nIs a Tor proxy already running on port " + TorService.PROXY_PORT + "?"; }