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()) {
mode = optionalMode.get();
Config.get().setMode(mode);
Config.get().setCoreWallet(Bwt.DEFAULT_CORE_WALLET);
if(mode.equals(Mode.ONLINE)) {
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) {
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) {

View file

@ -425,24 +425,6 @@ public class Config {
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() {
return electrumServer;
}

View file

@ -1,5 +1,6 @@
package com.sparrowwallet.sparrow.net;
import com.google.common.net.HostAndPort;
import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
import com.sparrowwallet.drongo.KeyPurpose;
@ -7,6 +8,7 @@ import com.sparrowwallet.drongo.Network;
import com.sparrowwallet.drongo.OutputDescriptor;
import com.sparrowwallet.drongo.wallet.BlockTransactionHash;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.io.Config;
@ -100,6 +102,7 @@ public class Bwt {
bwtConfig.gapLimit = gapLimit;
} else {
bwtConfig.requireAddresses = false;
bwtConfig.bitcoindTimeout = 30;
}
bwtConfig.verbose = log.isDebugEnabled() ? 2 : 0;
@ -112,14 +115,19 @@ public class Bwt {
Config config = Config.get();
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) {
bwtConfig.bitcoindDir = config.getCoreDataDir().getAbsolutePath() + "/";
} else {
bwtConfig.bitcoindAuth = config.getCoreAuth();
}
if(config.getCoreMultiWallet() != Boolean.FALSE) {
bwtConfig.bitcoindWallet = config.getCoreWallet();
}
bwtConfig.bitcoindWallet = DEFAULT_CORE_WALLET;
bwtConfig.createWalletIfMissing = true;
Gson gson = new Gson();
@ -129,6 +137,12 @@ public class Bwt {
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
*
@ -184,6 +198,12 @@ public class Bwt {
@SerializedName("bitcoind_wallet")
public String bitcoindWallet;
@SerializedName("bitcoind_proxy")
public String bitcoindProxy;
@SerializedName("bitcoind_timeout")
public Integer bitcoindTimeout;
@SerializedName("create_wallet_if_missing")
public Boolean createWalletIfMissing;

View file

@ -102,6 +102,6 @@ public final class IpAddressMatcher {
}
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();
}
public boolean isOnionAddress(HostAndPort server) {
public static boolean isOnionAddress(HostAndPort server) {
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) {
if(url.startsWith("tcp://")) {
return TCP;

View file

@ -26,11 +26,13 @@ public class TcpTransport implements Transport, Closeable {
public static final int DEFAULT_PORT = 50001;
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 int SOCKET_READ_TIMEOUT_MILLIS = 5000;
protected final HostAndPort server;
protected final SocketFactory socketFactory;
protected final int[] readTimeouts;
private Socket socket;
@ -59,6 +61,7 @@ public class TcpTransport implements Transport, Closeable {
public TcpTransport(HostAndPort server, HostAndPort proxy) {
this.server = server;
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
@ -91,16 +94,16 @@ public class TcpTransport implements Transport, Closeable {
private String readResponse() throws IOException {
try {
if(!readLock.tryLock((BASE_READ_TIMEOUT_SECS[readTimeoutIndex] * 1000) + (requestIdCount * PER_REQUEST_READ_TIMEOUT_MILLIS), TimeUnit.MILLISECONDS)) {
readTimeoutIndex = Math.min(readTimeoutIndex + 1, BASE_READ_TIMEOUT_SECS.length - 1);
log.warn("No response from server, setting read timeout to " + BASE_READ_TIMEOUT_SECS[readTimeoutIndex] + " secs");
if(!readLock.tryLock((readTimeouts[readTimeoutIndex] * 1000L) + (requestIdCount * PER_REQUEST_READ_TIMEOUT_MILLIS), TimeUnit.MILLISECONDS)) {
readTimeoutIndex = Math.min(readTimeoutIndex + 1, readTimeouts.length - 1);
log.warn("No response from server, setting read timeout to " + readTimeouts[readTimeoutIndex] + " secs");
throw new IOException("No response from server");
}
} catch(InterruptedException e) {
throw new IOException("Read thread interrupted");
}
if(readTimeoutIndex == BASE_READ_TIMEOUT_SECS.length - 1) {
if(readTimeoutIndex == readTimeouts.length - 1) {
readTimeoutIndex--;
}

View file

@ -102,10 +102,13 @@ public class ServerPreferencesController extends PreferencesDetailController {
private PasswordField corePass;
@FXML
private UnlabeledToggleSwitch coreMultiWallet;
private UnlabeledToggleSwitch coreUseProxy;
@FXML
private TextField coreWallet;
private TextField coreProxyHost;
@FXML
private TextField coreProxyPort;
@FXML
private Form electrumForm;
@ -209,7 +212,9 @@ public class ServerPreferencesController extends PreferencesDetailController {
coreUser.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));
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) -> {
setElectrumServerInConfig(config);
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();
if(electrumServer != null) {
Protocol protocol = Protocol.getProtocol(electrumServer);
@ -519,8 +510,9 @@ public class ServerPreferencesController extends PreferencesDetailController {
coreDataDirSelect.setDisable(!editable);
coreUser.setDisable(!editable);
corePass.setDisable(!editable);
coreMultiWallet.setDisable(!editable);
coreWallet.setDisable(!editable);
coreUseProxy.setDisable(!editable);
coreProxyHost.setDisable(!editable);
coreProxyPort.setDisable(!editable);
electrumHost.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
private ChangeListener<String> getElectrumServerListener(Config config) {
return (observable, oldValue, newValue) -> {

View file

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