mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-24 12:46:45 +00:00
add built in tor
This commit is contained in:
parent
4bc446724d
commit
2e0ca1b4fa
17 changed files with 190 additions and 19 deletions
15
build.gradle
15
build.gradle
|
@ -9,7 +9,7 @@ def sparrowVersion = '0.9.3'
|
|||
def os = org.gradle.internal.os.OperatingSystem.current()
|
||||
def osName = os.getFamilyName()
|
||||
if(os.macOsX) {
|
||||
osName = "mac"
|
||||
osName = "osx"
|
||||
}
|
||||
|
||||
group "com.sparrowwallet"
|
||||
|
@ -19,6 +19,7 @@ repositories {
|
|||
mavenCentral()
|
||||
maven { url 'https://oss.sonatype.org/content/groups/public' }
|
||||
maven { url 'https://mymavenrepo.com/repo/29EACwkkGcoOKnbx3bxN/' }
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
|
||||
tasks.withType(AbstractArchiveTask) {
|
||||
|
@ -54,6 +55,7 @@ dependencies {
|
|||
implementation('com.github.sarxos:webcam-capture:0.3.13-SNAPSHOT') {
|
||||
exclude group: 'com.nativelibs4java', module: 'bridj'
|
||||
}
|
||||
implementation("com.sparrowwallet:netlayer-jpms-${osName}:0.6.8")
|
||||
implementation('de.codecentric.centerdevice:centerdevice-nsmenufx:2.1.7')
|
||||
implementation('org.controlsfx:controlsfx:11.0.2' ) {
|
||||
exclude group: 'org.openjfx', module: 'javafx-base'
|
||||
|
@ -80,8 +82,8 @@ compileJava {
|
|||
|
||||
processResources {
|
||||
doLast {
|
||||
delete fileTree("$buildDir/resources/main/external").matching {
|
||||
exclude "$osName/**"
|
||||
delete fileTree("$buildDir/resources/main/native").matching {
|
||||
exclude "${osName}/**"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -101,7 +103,8 @@ run {
|
|||
"--add-opens=javafx.controls/com.sun.javafx.scene.control=centerdevice.nsmenufx",
|
||||
"--add-opens=javafx.graphics/com.sun.javafx.menu=centerdevice.nsmenufx",
|
||||
"--add-opens=javafx.graphics/com.sun.glass.ui=com.sparrowwallet.sparrow",
|
||||
"--add-opens=javafx.graphics/com.sun.javafx.application=com.sparrowwallet.sparrow"]
|
||||
"--add-opens=javafx.graphics/com.sun.javafx.application=com.sparrowwallet.sparrow",
|
||||
"--add-opens=java.base/java.net=com.sparrowwallet.sparrow"]
|
||||
|
||||
if(os.macOsX) {
|
||||
applicationDefaultJvmArgs += ["-Xdock:name=Sparrow", "-Xdock:icon=/Users/scy/git/sparrow/src/main/resources/sparrow.png",
|
||||
|
@ -118,6 +121,7 @@ jlink {
|
|||
requires 'javafx.base'
|
||||
requires 'com.fasterxml.jackson.databind'
|
||||
requires 'jdk.crypto.cryptoki'
|
||||
requires 'java.management'
|
||||
}
|
||||
|
||||
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages', '--ignore-signing-information', '--exclude-files', '**.png']
|
||||
|
@ -139,6 +143,7 @@ jlink {
|
|||
"--add-opens=javafx.graphics/com.sun.glass.ui=com.sparrowwallet.sparrow",
|
||||
"--add-opens=javafx.graphics/com.sun.glass.ui.mac=com.sparrowwallet.merged.module",
|
||||
"--add-opens=javafx.graphics/com.sun.javafx.application=com.sparrowwallet.sparrow",
|
||||
"--add-opens=java.base/java.net=com.sparrowwallet.sparrow",
|
||||
"--add-reads=com.sparrowwallet.merged.module=java.desktop"]
|
||||
}
|
||||
addExtraDependencies("javafx")
|
||||
|
@ -160,7 +165,7 @@ jlink {
|
|||
}
|
||||
if(os.macOsX) {
|
||||
installerOptions += ['--mac-sign', '--mac-signing-key-user-name', 'Craig Raw (UPLVMSK9D7)']
|
||||
imageOptions += ['--icon', 'src/main/deploy/package/mac/sparrow.icns', '--resource-dir', 'src/main/deploy/package/mac/']
|
||||
imageOptions += ['--icon', 'src/main/deploy/package/osx/sparrow.icns', '--resource-dir', 'src/main/deploy/package/osx/']
|
||||
installerType = "dmg"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -280,6 +280,13 @@ public class AppController implements Initializable {
|
|||
connectionService.setPeriod(new Duration(SERVER_PING_PERIOD));
|
||||
connectionService.setRestartOnFailure(true);
|
||||
|
||||
EventManager.get().register(connectionService);
|
||||
connectionService.statusProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if(connectionService.isRunning()) {
|
||||
EventManager.get().post(new StatusEvent(newValue));
|
||||
}
|
||||
});
|
||||
|
||||
connectionService.setOnSucceeded(successEvent -> {
|
||||
changeMode = false;
|
||||
onlineProperty.setValue(true);
|
||||
|
@ -350,8 +357,8 @@ public class AppController implements Initializable {
|
|||
tk.createQuitMenuItem(MainApp.APP_NAME));
|
||||
tk.setApplicationMenu(defaultApplicationMenu);
|
||||
|
||||
fileMenu.getItems().removeIf(item -> item.getStyleClass().contains("macHide"));
|
||||
helpMenu.getItems().removeIf(item -> item.getStyleClass().contains("macHide"));
|
||||
fileMenu.getItems().removeIf(item -> item.getStyleClass().contains("osxHide"));
|
||||
helpMenu.getItems().removeIf(item -> item.getStyleClass().contains("osxHide"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1155,7 +1162,7 @@ public class AppController implements Initializable {
|
|||
public void statusUpdated(StatusEvent event) {
|
||||
statusBar.setText(event.getStatus());
|
||||
|
||||
PauseTransition wait = new PauseTransition(Duration.seconds(10));
|
||||
PauseTransition wait = new PauseTransition(Duration.seconds(20));
|
||||
wait.setOnFinished((e) -> {
|
||||
if(statusBar.getText().equals(event.getStatus())) {
|
||||
statusBar.setText("");
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package com.sparrowwallet.sparrow.event;
|
||||
|
||||
public class TorStatusEvent {
|
||||
private final String status;
|
||||
|
||||
public TorStatusEvent(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
}
|
|
@ -23,7 +23,6 @@ import java.nio.file.attribute.PosixFilePermission;
|
|||
import java.nio.file.attribute.PosixFilePermissions;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
|
@ -200,7 +199,7 @@ public class Hwi {
|
|||
File hwiExecutable = Config.get().getHwi();
|
||||
if(hwiExecutable != null && hwiExecutable.exists()) {
|
||||
if(command.isTestFirst() && (!hwiExecutable.getAbsolutePath().contains(TEMP_FILE_PREFIX) || !testHwi(hwiExecutable))) {
|
||||
if(Platform.getCurrent().getPlatformId().toLowerCase().equals("mac")) {
|
||||
if(Platform.getCurrent() == Platform.OSX) {
|
||||
deleteDirectory(hwiExecutable.getParentFile());
|
||||
} else {
|
||||
hwiExecutable.delete();
|
||||
|
@ -218,7 +217,7 @@ public class Hwi {
|
|||
//The check will still happen on first invocation, but will not thereafter
|
||||
//See https://github.com/bitcoin-core/HWI/issues/327 for details
|
||||
if(platform == Platform.OSX) {
|
||||
InputStream inputStream = Hwi.class.getResourceAsStream("/external/mac/hwi-1.1.2-mac-amd64-signed.zip");
|
||||
InputStream inputStream = Hwi.class.getResourceAsStream("/native/osx/x64/hwi-1.1.2-mac-amd64-signed.zip");
|
||||
Path tempHwiDirPath = Files.createTempDirectory(TEMP_FILE_PREFIX, PosixFilePermissions.asFileAttribute(ownerExecutableWritable));
|
||||
File tempHwiDir = tempHwiDirPath.toFile();
|
||||
//tempHwiDir.deleteOnExit();
|
||||
|
@ -249,10 +248,10 @@ public class Hwi {
|
|||
InputStream inputStream;
|
||||
Path tempExecPath;
|
||||
if(platform == Platform.WINDOWS) {
|
||||
inputStream = Hwi.class.getResourceAsStream("/external/windows/hwi.exe");
|
||||
inputStream = Hwi.class.getResourceAsStream("/native/windows/x64/hwi.exe");
|
||||
tempExecPath = Files.createTempFile(TEMP_FILE_PREFIX, null);
|
||||
} else {
|
||||
inputStream = Hwi.class.getResourceAsStream("/external/linux/hwi");
|
||||
inputStream = Hwi.class.getResourceAsStream("/native/linux/x64/hwi");
|
||||
tempExecPath = Files.createTempFile(TEMP_FILE_PREFIX, null, PosixFilePermissions.asFileAttribute(ownerExecutableWritable));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.sparrowwallet.sparrow.net;
|
||||
|
||||
import com.github.arteam.simplejsonrpc.client.Transport;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import com.google.common.net.HostAndPort;
|
||||
import com.sparrowwallet.drongo.KeyPurpose;
|
||||
import com.sparrowwallet.drongo.Utils;
|
||||
|
@ -8,8 +9,11 @@ import com.sparrowwallet.drongo.protocol.*;
|
|||
import com.sparrowwallet.drongo.wallet.*;
|
||||
import com.sparrowwallet.sparrow.event.ConnectionEvent;
|
||||
import com.sparrowwallet.sparrow.event.FeeRatesUpdatedEvent;
|
||||
import com.sparrowwallet.sparrow.event.TorStatusEvent;
|
||||
import com.sparrowwallet.sparrow.io.Config;
|
||||
import com.sparrowwallet.sparrow.wallet.SendController;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.concurrent.ScheduledService;
|
||||
import javafx.concurrent.Service;
|
||||
import javafx.concurrent.Task;
|
||||
|
@ -578,6 +582,7 @@ public class ElectrumServer {
|
|||
private boolean firstCall = true;
|
||||
private Thread reader;
|
||||
private long feeRatesRetrievedAt;
|
||||
private StringProperty statusProperty = new SimpleStringProperty();
|
||||
|
||||
public ConnectionService() {
|
||||
this(true);
|
||||
|
@ -675,6 +680,15 @@ public class ElectrumServer {
|
|||
public void uncaughtException(Thread t, Throwable e) {
|
||||
log.error("Uncaught error in ConnectionService", e);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void torStatus(TorStatusEvent event) {
|
||||
statusProperty.set(event.getStatus());
|
||||
}
|
||||
|
||||
public StringProperty statusProperty() {
|
||||
return statusProperty;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ReadRunnable implements Runnable {
|
||||
|
|
|
@ -14,12 +14,16 @@ public enum Protocol {
|
|||
TCP {
|
||||
@Override
|
||||
public Transport getTransport(HostAndPort server) {
|
||||
if(isOnionAddress(server)) {
|
||||
return new TorTcpTransport(server);
|
||||
}
|
||||
|
||||
return new TcpTransport(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Transport getTransport(HostAndPort server, File serverCert) {
|
||||
return new TcpTransport(server);
|
||||
return getTransport(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -35,11 +39,19 @@ public enum Protocol {
|
|||
SSL {
|
||||
@Override
|
||||
public Transport getTransport(HostAndPort server) throws KeyManagementException, NoSuchAlgorithmException {
|
||||
if(isOnionAddress(server)) {
|
||||
return new TorTcpOverTlsTransport(server);
|
||||
}
|
||||
|
||||
return new TcpOverTlsTransport(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Transport getTransport(HostAndPort server, File serverCert) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
|
||||
if(isOnionAddress(server)) {
|
||||
return new TorTcpOverTlsTransport(server, serverCert);
|
||||
}
|
||||
|
||||
return new TcpOverTlsTransport(server, serverCert);
|
||||
}
|
||||
|
||||
|
@ -82,6 +94,10 @@ public enum Protocol {
|
|||
return toUrlString() + hostAndPort.toString();
|
||||
}
|
||||
|
||||
public boolean isOnionAddress(HostAndPort server) {
|
||||
return server.getHost().toLowerCase().endsWith(".onion");
|
||||
}
|
||||
|
||||
public static Protocol getProtocol(String url) {
|
||||
if(url.startsWith("tcp://")) {
|
||||
return TCP;
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
package com.sparrowwallet.sparrow.net;
|
||||
|
||||
import com.google.common.net.HostAndPort;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.Socket;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
public class TorTcpOverTlsTransport extends TcpOverTlsTransport {
|
||||
private static final Logger log = LoggerFactory.getLogger(TorTcpOverTlsTransport.class);
|
||||
|
||||
public TorTcpOverTlsTransport(HostAndPort server) throws NoSuchAlgorithmException, KeyManagementException {
|
||||
super(server);
|
||||
}
|
||||
|
||||
public TorTcpOverTlsTransport(HostAndPort server, File crtFile) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
|
||||
super(server, crtFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Socket createSocket() throws IOException {
|
||||
TorTcpTransport torTcpTransport = new TorTcpTransport(server);
|
||||
Socket socket = torTcpTransport.createSocket();
|
||||
|
||||
try {
|
||||
Field socketField = socket.getClass().getDeclaredField("socket");
|
||||
socketField.setAccessible(true);
|
||||
Socket innerSocket = (Socket)socketField.get(socket);
|
||||
Field connectedField = innerSocket.getClass().getSuperclass().getDeclaredField("connected");
|
||||
connectedField.setAccessible(true);
|
||||
connectedField.set(innerSocket, true);
|
||||
} catch(Exception e) {
|
||||
log.error("Could not set socket connected status", e);
|
||||
}
|
||||
|
||||
SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(socket, server.getHost(), server.getPortOrDefault(DEFAULT_PORT), true);
|
||||
sslSocket.startHandshake();
|
||||
|
||||
return sslSocket;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package com.sparrowwallet.sparrow.net;
|
||||
|
||||
import com.google.common.net.HostAndPort;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.event.StatusEvent;
|
||||
import com.sparrowwallet.sparrow.event.TorStatusEvent;
|
||||
import javafx.application.Platform;
|
||||
import org.berndpruenster.netlayer.tor.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.Socket;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
public class TorTcpTransport extends TcpTransport {
|
||||
public static final String TOR_DIR_PREFIX = "tor";
|
||||
|
||||
public TorTcpTransport(HostAndPort server) {
|
||||
super(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Socket createSocket() throws IOException {
|
||||
if(Tor.getDefault() == null) {
|
||||
Platform.runLater(() -> {
|
||||
String status = "Starting Tor...";
|
||||
EventManager.get().post(new TorStatusEvent(status));
|
||||
});
|
||||
|
||||
Path path = Files.createTempDirectory(TOR_DIR_PREFIX);
|
||||
File torInstallDir = path.toFile();
|
||||
torInstallDir.deleteOnExit();
|
||||
try {
|
||||
LinkedHashMap<String, String> torrcOptionsMap = new LinkedHashMap<>();
|
||||
torrcOptionsMap.put("DisableNetwork", "0");
|
||||
Torrc override = new Torrc(torrcOptionsMap);
|
||||
|
||||
NativeTor nativeTor = new NativeTor(torInstallDir, Collections.emptyList(), override);
|
||||
Tor.setDefault(nativeTor);
|
||||
} catch(TorCtlException e) {
|
||||
e.printStackTrace();
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
Platform.runLater(() -> {
|
||||
String status = "Tor running, connecting to " + server.toString() + "...";
|
||||
EventManager.get().post(new TorStatusEvent(status));
|
||||
});
|
||||
|
||||
return new TorSocket(server.getHost(), server.getPort(), "sparrow");
|
||||
}
|
||||
}
|
|
@ -28,6 +28,8 @@ import org.controlsfx.validation.ValidationSupport;
|
|||
import org.controlsfx.validation.Validator;
|
||||
import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
import java.io.File;
|
||||
|
@ -36,6 +38,8 @@ import java.security.cert.CertificateFactory;
|
|||
import java.util.List;
|
||||
|
||||
public class ServerPreferencesController extends PreferencesDetailController {
|
||||
private static final Logger log = LoggerFactory.getLogger(ServerPreferencesController.class);
|
||||
|
||||
@FXML
|
||||
private TextField host;
|
||||
|
||||
|
@ -140,13 +144,20 @@ public class ServerPreferencesController extends PreferencesDetailController {
|
|||
testConnection.setGraphic(getGlyph(FontAwesome5.Glyph.ELLIPSIS_H, null));
|
||||
|
||||
ElectrumServer.ConnectionService connectionService = new ElectrumServer.ConnectionService(false);
|
||||
connectionService.setPeriod(Duration.minutes(1));
|
||||
connectionService.setPeriod(Duration.ZERO);
|
||||
EventManager.get().register(connectionService);
|
||||
connectionService.statusProperty().addListener((observable, oldValue, newValue) -> {
|
||||
testResults.setText(testResults.getText() + "\n" + newValue);
|
||||
});
|
||||
|
||||
connectionService.setOnSucceeded(successEvent -> {
|
||||
EventManager.get().unregister(connectionService);
|
||||
ConnectionEvent connectionEvent = (ConnectionEvent)connectionService.getValue();
|
||||
showConnectionSuccess(connectionEvent.getServerVersion(), connectionEvent.getServerBanner());
|
||||
connectionService.cancel();
|
||||
});
|
||||
connectionService.setOnFailed(workerStateEvent -> {
|
||||
EventManager.get().unregister(connectionService);
|
||||
showConnectionFailure(workerStateEvent);
|
||||
connectionService.cancel();
|
||||
});
|
||||
|
@ -230,6 +241,7 @@ public class ServerPreferencesController extends PreferencesDetailController {
|
|||
|
||||
private void showConnectionFailure(WorkerStateEvent failEvent) {
|
||||
Throwable e = failEvent.getSource().getException();
|
||||
log.error("Connection error", e);
|
||||
String reason = e.getCause() != null ? e.getCause().getMessage() : e.getMessage();
|
||||
if(e.getCause() != null && e.getCause() instanceof SSLHandshakeException) {
|
||||
reason = "SSL Handshake Error\n" + reason;
|
||||
|
|
|
@ -20,6 +20,7 @@ open module com.sparrowwallet.sparrow {
|
|||
requires com.fasterxml.jackson.databind;
|
||||
requires cbor;
|
||||
requires webcam.capture;
|
||||
requires netlayer.jpms;
|
||||
requires centerdevice.nsmenufx;
|
||||
requires slf4j.api;
|
||||
}
|
|
@ -29,11 +29,11 @@
|
|||
<SeparatorMenuItem />
|
||||
<MenuItem mnemonicParsing="false" text="Import Wallet..." onAction="#importWallet"/>
|
||||
<MenuItem fx:id="exportWallet" mnemonicParsing="false" text="Export Wallet..." onAction="#exportWallet"/>
|
||||
<SeparatorMenuItem styleClass="macHide" />
|
||||
<MenuItem styleClass="macHide" mnemonicParsing="false" text="Preferences..." onAction="#openPreferences"/>
|
||||
<SeparatorMenuItem styleClass="osxHide" />
|
||||
<MenuItem styleClass="osxHide" mnemonicParsing="false" text="Preferences..." onAction="#openPreferences"/>
|
||||
<SeparatorMenuItem />
|
||||
<MenuItem mnemonicParsing="false" text="Close Tab" onAction="#closeTab"/>
|
||||
<MenuItem styleClass="macHide" mnemonicParsing="false" text="Quit" onAction="#quit"/>
|
||||
<MenuItem styleClass="osxHide" mnemonicParsing="false" text="Quit" onAction="#quit"/>
|
||||
</items>
|
||||
</Menu>
|
||||
<fx:define>
|
||||
|
@ -67,7 +67,7 @@
|
|||
<MenuItem mnemonicParsing="false" text="Sign/Verify Message" onAction="#signVerifyMessage"/>
|
||||
</Menu>
|
||||
<Menu fx:id="helpMenu" mnemonicParsing="false" text="Help">
|
||||
<MenuItem styleClass="macHide" mnemonicParsing="false" text="About Sparrow" onAction="#showAbout"/>
|
||||
<MenuItem styleClass="osxHide" mnemonicParsing="false" text="About Sparrow" onAction="#showAbout"/>
|
||||
</Menu>
|
||||
</menus>
|
||||
</MenuBar>
|
||||
|
|
Loading…
Reference in a new issue