support bitcoin core connections over tor

This commit is contained in:
Craig Raw 2021-10-06 20:37:58 +02:00
parent 576253e651
commit 6f95dbe309
12 changed files with 62 additions and 61 deletions

View file

@ -70,7 +70,6 @@ public class MainApp extends Application {
if(optionalMode.isPresent()) { if(optionalMode.isPresent()) {
mode = optionalMode.get(); mode = optionalMode.get();
Config.get().setMode(mode); Config.get().setMode(mode);
Config.get().setCoreWallet(Bwt.DEFAULT_CORE_WALLET);
if(mode.equals(Mode.ONLINE)) { if(mode.equals(Mode.ONLINE)) {
PreferencesDialog preferencesDialog = new PreferencesDialog(PreferenceGroup.SERVER, true); PreferencesDialog preferencesDialog = new PreferencesDialog(PreferenceGroup.SERVER, true);
@ -85,9 +84,6 @@ public class MainApp extends Application {
if(Config.get().getServerType() == null && Config.get().getCoreServer() == null && Config.get().getElectrumServer() != null) { if(Config.get().getServerType() == null && Config.get().getCoreServer() == null && Config.get().getElectrumServer() != null) {
Config.get().setServerType(ServerType.ELECTRUM_SERVER); Config.get().setServerType(ServerType.ELECTRUM_SERVER);
} else if(Config.get().getServerType() == ServerType.BITCOIN_CORE && Config.get().getCoreWallet() == null) {
Config.get().setCoreMultiWallet(Boolean.TRUE);
Config.get().setCoreWallet("");
} }
if(Config.get().getHdCapture() == null && Platform.getCurrent() == Platform.OSX) { if(Config.get().getHdCapture() == null && Platform.getCurrent() == Platform.OSX) {

View file

@ -425,24 +425,6 @@ public class Config {
flush(); flush();
} }
public Boolean getCoreMultiWallet() {
return coreMultiWallet;
}
public void setCoreMultiWallet(Boolean coreMultiWallet) {
this.coreMultiWallet = coreMultiWallet;
flush();
}
public String getCoreWallet() {
return coreWallet;
}
public void setCoreWallet(String coreWallet) {
this.coreWallet = coreWallet;
flush();
}
public String getElectrumServer() { public String getElectrumServer() {
return electrumServer; return electrumServer;
} }

View file

@ -1,5 +1,6 @@
package com.sparrowwallet.sparrow.net; package com.sparrowwallet.sparrow.net;
import com.google.common.net.HostAndPort;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import com.sparrowwallet.drongo.KeyPurpose; import com.sparrowwallet.drongo.KeyPurpose;
@ -7,6 +8,7 @@ import com.sparrowwallet.drongo.Network;
import com.sparrowwallet.drongo.OutputDescriptor; import com.sparrowwallet.drongo.OutputDescriptor;
import com.sparrowwallet.drongo.wallet.BlockTransactionHash; import com.sparrowwallet.drongo.wallet.BlockTransactionHash;
import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.io.Config; import com.sparrowwallet.sparrow.io.Config;
@ -100,6 +102,7 @@ public class Bwt {
bwtConfig.gapLimit = gapLimit; bwtConfig.gapLimit = gapLimit;
} else { } else {
bwtConfig.requireAddresses = false; bwtConfig.requireAddresses = false;
bwtConfig.bitcoindTimeout = 30;
} }
bwtConfig.verbose = log.isDebugEnabled() ? 2 : 0; bwtConfig.verbose = log.isDebugEnabled() ? 2 : 0;
@ -112,14 +115,19 @@ public class Bwt {
Config config = Config.get(); Config config = Config.get();
bwtConfig.bitcoindUrl = config.getCoreServer(); bwtConfig.bitcoindUrl = config.getCoreServer();
HostAndPort torProxy = getTorProxy();
if(Protocol.isOnionAddress(bwtConfig.bitcoindUrl) && torProxy != null) {
bwtConfig.bitcoindProxy = torProxy.toString();
}
if((config.getCoreAuthType() == CoreAuthType.COOKIE || config.getCoreAuth() == null || config.getCoreAuth().length() < 2) && config.getCoreDataDir() != null) { if((config.getCoreAuthType() == CoreAuthType.COOKIE || config.getCoreAuth() == null || config.getCoreAuth().length() < 2) && config.getCoreDataDir() != null) {
bwtConfig.bitcoindDir = config.getCoreDataDir().getAbsolutePath() + "/"; bwtConfig.bitcoindDir = config.getCoreDataDir().getAbsolutePath() + "/";
} else { } else {
bwtConfig.bitcoindAuth = config.getCoreAuth(); bwtConfig.bitcoindAuth = config.getCoreAuth();
} }
if(config.getCoreMultiWallet() != Boolean.FALSE) {
bwtConfig.bitcoindWallet = config.getCoreWallet(); bwtConfig.bitcoindWallet = DEFAULT_CORE_WALLET;
}
bwtConfig.createWalletIfMissing = true; bwtConfig.createWalletIfMissing = true;
Gson gson = new Gson(); Gson gson = new Gson();
@ -129,6 +137,12 @@ public class Bwt {
NativeBwtDaemon.start(jsonConfig, callback); NativeBwtDaemon.start(jsonConfig, callback);
} }
private HostAndPort getTorProxy() {
return AppServices.isTorRunning() ?
HostAndPort.fromParts("127.0.0.1", TorService.PROXY_PORT) :
(Config.get().getProxyServer() == null || Config.get().getProxyServer().isEmpty() || !Config.get().isUseProxy() ? null : HostAndPort.fromString(Config.get().getProxyServer().replace("localhost", "127.0.0.1")));
}
/** /**
* Shut down the BWT daemon * Shut down the BWT daemon
* *
@ -184,6 +198,12 @@ public class Bwt {
@SerializedName("bitcoind_wallet") @SerializedName("bitcoind_wallet")
public String bitcoindWallet; public String bitcoindWallet;
@SerializedName("bitcoind_proxy")
public String bitcoindProxy;
@SerializedName("bitcoind_timeout")
public Integer bitcoindTimeout;
@SerializedName("create_wallet_if_missing") @SerializedName("create_wallet_if_missing")
public Boolean createWalletIfMissing; public Boolean createWalletIfMissing;

View file

@ -102,6 +102,6 @@ public final class IpAddressMatcher {
} }
public static boolean isLocalNetworkAddress(String address) { public static boolean isLocalNetworkAddress(String address) {
return LOCAL_RANGE_1.matches(address) || LOCAL_RANGE_2.matches(address) || LOCAL_RANGE_3.matches(address); return "localhost".equals(address) || "127.0.0.1".equals(address) || LOCAL_RANGE_1.matches(address) || LOCAL_RANGE_2.matches(address) || LOCAL_RANGE_3.matches(address);
} }
} }

View file

@ -116,10 +116,21 @@ public enum Protocol {
return toUrlString() + hostAndPort.toString(); return toUrlString() + hostAndPort.toString();
} }
public boolean isOnionAddress(HostAndPort server) { public static boolean isOnionAddress(HostAndPort server) {
return server.getHost().toLowerCase().endsWith(TorService.TOR_ADDRESS_SUFFIX); return server.getHost().toLowerCase().endsWith(TorService.TOR_ADDRESS_SUFFIX);
} }
public static boolean isOnionAddress(String address) {
if(address != null) {
Protocol protocol = Protocol.getProtocol(address);
if(protocol != null) {
return isOnionAddress(protocol.getServerHostAndPort(address));
}
}
return false;
}
public static Protocol getProtocol(String url) { public static Protocol getProtocol(String url) {
if(url.startsWith("tcp://")) { if(url.startsWith("tcp://")) {
return TCP; return TCP;

View file

@ -26,11 +26,13 @@ public class TcpTransport implements Transport, Closeable {
public static final int DEFAULT_PORT = 50001; public static final int DEFAULT_PORT = 50001;
private static final int[] BASE_READ_TIMEOUT_SECS = {3, 8, 16, 34}; private static final int[] BASE_READ_TIMEOUT_SECS = {3, 8, 16, 34};
private static final int[] SLOW_READ_TIMEOUT_SECS = {34, 68, 124, 208};
public static final long PER_REQUEST_READ_TIMEOUT_MILLIS = 50; public static final long PER_REQUEST_READ_TIMEOUT_MILLIS = 50;
public static final int SOCKET_READ_TIMEOUT_MILLIS = 5000; public static final int SOCKET_READ_TIMEOUT_MILLIS = 5000;
protected final HostAndPort server; protected final HostAndPort server;
protected final SocketFactory socketFactory; protected final SocketFactory socketFactory;
protected final int[] readTimeouts;
private Socket socket; private Socket socket;
@ -59,6 +61,7 @@ public class TcpTransport implements Transport, Closeable {
public TcpTransport(HostAndPort server, HostAndPort proxy) { public TcpTransport(HostAndPort server, HostAndPort proxy) {
this.server = server; this.server = server;
this.socketFactory = (proxy == null ? SocketFactory.getDefault() : new ProxySocketFactory(proxy)); this.socketFactory = (proxy == null ? SocketFactory.getDefault() : new ProxySocketFactory(proxy));
this.readTimeouts = (Config.get().getServerType() == ServerType.BITCOIN_CORE && Protocol.isOnionAddress(Config.get().getCoreServer()) ? SLOW_READ_TIMEOUT_SECS : BASE_READ_TIMEOUT_SECS);
} }
@Override @Override
@ -91,16 +94,16 @@ public class TcpTransport implements Transport, Closeable {
private String readResponse() throws IOException { private String readResponse() throws IOException {
try { try {
if(!readLock.tryLock((BASE_READ_TIMEOUT_SECS[readTimeoutIndex] * 1000) + (requestIdCount * PER_REQUEST_READ_TIMEOUT_MILLIS), TimeUnit.MILLISECONDS)) { if(!readLock.tryLock((readTimeouts[readTimeoutIndex] * 1000L) + (requestIdCount * PER_REQUEST_READ_TIMEOUT_MILLIS), TimeUnit.MILLISECONDS)) {
readTimeoutIndex = Math.min(readTimeoutIndex + 1, BASE_READ_TIMEOUT_SECS.length - 1); readTimeoutIndex = Math.min(readTimeoutIndex + 1, readTimeouts.length - 1);
log.warn("No response from server, setting read timeout to " + BASE_READ_TIMEOUT_SECS[readTimeoutIndex] + " secs"); log.warn("No response from server, setting read timeout to " + readTimeouts[readTimeoutIndex] + " secs");
throw new IOException("No response from server"); throw new IOException("No response from server");
} }
} catch(InterruptedException e) { } catch(InterruptedException e) {
throw new IOException("Read thread interrupted"); throw new IOException("Read thread interrupted");
} }
if(readTimeoutIndex == BASE_READ_TIMEOUT_SECS.length - 1) { if(readTimeoutIndex == readTimeouts.length - 1) {
readTimeoutIndex--; readTimeoutIndex--;
} }

View file

@ -102,10 +102,13 @@ public class ServerPreferencesController extends PreferencesDetailController {
private PasswordField corePass; private PasswordField corePass;
@FXML @FXML
private UnlabeledToggleSwitch coreMultiWallet; private UnlabeledToggleSwitch coreUseProxy;
@FXML @FXML
private TextField coreWallet; private TextField coreProxyHost;
@FXML
private TextField coreProxyPort;
@FXML @FXML
private Form electrumForm; private Form electrumForm;
@ -209,7 +212,9 @@ public class ServerPreferencesController extends PreferencesDetailController {
coreUser.textProperty().addListener(getBitcoinAuthListener(config)); coreUser.textProperty().addListener(getBitcoinAuthListener(config));
corePass.textProperty().addListener(getBitcoinAuthListener(config)); corePass.textProperty().addListener(getBitcoinAuthListener(config));
coreWallet.textProperty().addListener(getBitcoinWalletListener(config)); coreUseProxy.selectedProperty().bindBidirectional(useProxy.selectedProperty());
coreProxyHost.textProperty().bindBidirectional(proxyHost.textProperty());
coreProxyPort.textProperty().bindBidirectional(proxyPort.textProperty());
electrumHost.textProperty().addListener(getElectrumServerListener(config)); electrumHost.textProperty().addListener(getElectrumServerListener(config));
electrumPort.textProperty().addListener(getElectrumServerListener(config)); electrumPort.textProperty().addListener(getElectrumServerListener(config));
@ -250,11 +255,6 @@ public class ServerPreferencesController extends PreferencesDetailController {
} }
}); });
coreMultiWallet.selectedProperty().addListener((observable, oldValue, newValue) -> {
config.setCoreMultiWallet(newValue);
coreWallet.setDisable(!newValue);
});
electrumUseSsl.selectedProperty().addListener((observable, oldValue, newValue) -> { electrumUseSsl.selectedProperty().addListener((observable, oldValue, newValue) -> {
setElectrumServerInConfig(config); setElectrumServerInConfig(config);
electrumCertificate.setDisable(!newValue); electrumCertificate.setDisable(!newValue);
@ -358,15 +358,6 @@ public class ServerPreferencesController extends PreferencesDetailController {
} }
} }
coreWallet.setPromptText("Default");
if(config.getCoreWallet() == null) {
coreWallet.setText(Bwt.DEFAULT_CORE_WALLET);
} else {
coreWallet.setText(config.getCoreWallet());
}
coreMultiWallet.setSelected(config.getCoreMultiWallet() != Boolean.FALSE);
String electrumServer = config.getElectrumServer(); String electrumServer = config.getElectrumServer();
if(electrumServer != null) { if(electrumServer != null) {
Protocol protocol = Protocol.getProtocol(electrumServer); Protocol protocol = Protocol.getProtocol(electrumServer);
@ -519,8 +510,9 @@ public class ServerPreferencesController extends PreferencesDetailController {
coreDataDirSelect.setDisable(!editable); coreDataDirSelect.setDisable(!editable);
coreUser.setDisable(!editable); coreUser.setDisable(!editable);
corePass.setDisable(!editable); corePass.setDisable(!editable);
coreMultiWallet.setDisable(!editable); coreUseProxy.setDisable(!editable);
coreWallet.setDisable(!editable); coreProxyHost.setDisable(!editable);
coreProxyPort.setDisable(!editable);
electrumHost.setDisable(!editable); electrumHost.setDisable(!editable);
electrumPort.setDisable(!editable); electrumPort.setDisable(!editable);
@ -665,13 +657,6 @@ public class ServerPreferencesController extends PreferencesDetailController {
}; };
} }
@NotNull
private ChangeListener<String> getBitcoinWalletListener(Config config) {
return (observable, oldValue, newValue) -> {
config.setCoreWallet(coreWallet.getText());
};
}
@NotNull @NotNull
private ChangeListener<String> getElectrumServerListener(Config config) { private ChangeListener<String> getElectrumServerListener(Config config) {
return (observable, oldValue, newValue) -> { return (observable, oldValue, newValue) -> {

View file

@ -49,3 +49,6 @@
-fx-text-fill: linear-gradient(to bottom, derive(#0b99c9, 30%), #0b99c9); -fx-text-fill: linear-gradient(to bottom, derive(#0b99c9, 30%), #0b99c9);
} }
#electrumUseSsl {
-fx-padding: 4 0 2 0;
}

View file

@ -144,11 +144,12 @@
<TextField fx:id="coreUser"/> <TextField fx:id="coreUser"/>
<PasswordField fx:id="corePass"/> <PasswordField fx:id="corePass"/>
</Field> </Field>
<Field text="Multi-Wallet:"> <Field text="Use Proxy:">
<UnlabeledToggleSwitch fx:id="coreMultiWallet"/> <HelpLabel helpText="Creates a new Bitcoin Core wallet with the following name (recommended to avoid conflicts)" /> <UnlabeledToggleSwitch fx:id="coreUseProxy"/>
</Field> </Field>
<Field text="Wallet Name:" styleClass="label-button"> <Field text="Proxy URL:">
<TextField fx:id="coreWallet"/> <TextField fx:id="coreProxyHost" />
<TextField fx:id="coreProxyPort" maxWidth="100" />
</Field> </Field>
</Fieldset> </Fieldset>
</Form> </Form>