diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index 9f176df1..dcdb44a1 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -542,7 +542,18 @@ public class AppController implements Initializable { } private void setServerToggleTooltip(Integer currentBlockHeight) { - serverToggle.setTooltip(new Tooltip(AppServices.isConnected() ? "Connected to " + Config.get().getServerAddress() + (currentBlockHeight != null ? " at height " + currentBlockHeight : "") : "Disconnected")); + Tooltip tooltip = new Tooltip(getServerToggleTooltipText(currentBlockHeight)); + tooltip.setShowDuration(Duration.seconds(15)); + serverToggle.setTooltip(tooltip); + } + + private String getServerToggleTooltipText(Integer currentBlockHeight) { + if(AppServices.isConnected()) { + return "Connected to " + Config.get().getServerAddress() + (currentBlockHeight != null ? " at height " + currentBlockHeight : "") + + (Config.get().getServerType() == ServerType.PUBLIC_ELECTRUM_SERVER ? "\nWarning! You are connected to a public server and sharing your transaction data with it.\nFor better privacy, consider using your own Bitcoin Core node or private Electrum server." : ""); + } + + return "Disconnected"; } public void newWallet(ActionEvent event) { @@ -1069,6 +1080,12 @@ public class AppController implements Initializable { } public void setServerType(ServerType serverType) { + if(serverType == ServerType.PUBLIC_ELECTRUM_SERVER && !serverToggle.getStyleClass().contains("public-server")) { + serverToggle.getStyleClass().add("public-server"); + } else { + serverToggle.getStyleClass().remove("public-server"); + } + if(serverType == ServerType.BITCOIN_CORE && !serverToggle.getStyleClass().contains("core-server")) { serverToggle.getStyleClass().add("core-server"); } else { diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Config.java b/src/main/java/com/sparrowwallet/sparrow/io/Config.java index 998c7f89..398b798c 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Config.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Config.java @@ -38,6 +38,7 @@ public class Config { private File hwi; private boolean hdCapture; private ServerType serverType; + private String publicElectrumServer; private String coreServer; private CoreAuthType coreAuthType; private File coreDataDir; @@ -275,7 +276,7 @@ public class Config { } public String getServerAddress() { - return getServerType() == ServerType.BITCOIN_CORE ? getCoreServer() : getElectrumServer(); + return getServerType() == ServerType.BITCOIN_CORE ? getCoreServer() : (getServerType() == ServerType.PUBLIC_ELECTRUM_SERVER ? getPublicElectrumServer() : getElectrumServer()); } public boolean requiresTor() { @@ -291,6 +292,15 @@ public class Config { return protocol.isOnionAddress(protocol.getServerHostAndPort(getServerAddress())); } + public String getPublicElectrumServer() { + return publicElectrumServer; + } + + public void setPublicElectrumServer(String publicElectrumServer) { + this.publicElectrumServer = publicElectrumServer; + flush(); + } + public String getCoreServer() { return coreServer; } diff --git a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java index b1fe278d..3e98c3b2 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java @@ -53,7 +53,10 @@ public class ElectrumServer { File electrumServerCert = null; String proxyServer = null; - if(Config.get().getServerType() == ServerType.BITCOIN_CORE) { + if(Config.get().getServerType() == ServerType.PUBLIC_ELECTRUM_SERVER) { + electrumServer = Config.get().getPublicElectrumServer(); + proxyServer = Config.get().getProxyServer(); + } else if(Config.get().getServerType() == ServerType.BITCOIN_CORE) { if(bwtElectrumServer == null) { throw new ServerConfigException("Could not connect to Bitcoin Core RPC"); } diff --git a/src/main/java/com/sparrowwallet/sparrow/net/PublicElectrumServer.java b/src/main/java/com/sparrowwallet/sparrow/net/PublicElectrumServer.java new file mode 100644 index 00000000..c4a778d0 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/net/PublicElectrumServer.java @@ -0,0 +1,38 @@ +package com.sparrowwallet.sparrow.net; + +public enum PublicElectrumServer { + BLOCKSTREAM_INFO("blockstream.info", "ssl://blockstream.info:700"), + ELECTRUM_BLOCKSTREAM_INFO("electrum.blockstream.info", "ssl://electrum.blockstream.info:50002"), + LUKECHILDS_CO("bitcoin.lukechilds.co", "ssl://bitcoin.lukechilds.co:50002"); + + PublicElectrumServer(String name, String url) { + this.name = name; + this.url = url; + } + + private final String name; + private final String url; + + public String getName() { + return name; + } + + public String getUrl() { + return url; + } + + public static PublicElectrumServer fromUrl(String url) { + for(PublicElectrumServer server : values()) { + if(server.url.equals(url)) { + return server; + } + } + + return null; + } + + @Override + public String toString() { + return name; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/net/ServerType.java b/src/main/java/com/sparrowwallet/sparrow/net/ServerType.java index dab5b9a9..225e18e5 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/ServerType.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/ServerType.java @@ -1,7 +1,7 @@ package com.sparrowwallet.sparrow.net; public enum ServerType { - BITCOIN_CORE("Bitcoin Core"), ELECTRUM_SERVER("Electrum Server"); + BITCOIN_CORE("Bitcoin Core"), ELECTRUM_SERVER("Private Electrum"), PUBLIC_ELECTRUM_SERVER("Public Electrum"); private final String name; diff --git a/src/main/java/com/sparrowwallet/sparrow/preferences/PreferencesDialog.java b/src/main/java/com/sparrowwallet/sparrow/preferences/PreferencesDialog.java index 9af84ec5..760d04c2 100644 --- a/src/main/java/com/sparrowwallet/sparrow/preferences/PreferencesDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/preferences/PreferencesDialog.java @@ -46,7 +46,7 @@ public class PreferencesDialog extends Dialog { dialogPane.getButtonTypes().addAll(newWalletButtonType); } - dialogPane.setPrefWidth(650); + dialogPane.setPrefWidth(710); dialogPane.setPrefHeight(600); preferencesController.reconnectOnClosingProperty().set(AppServices.isConnecting() || AppServices.isConnected()); diff --git a/src/main/java/com/sparrowwallet/sparrow/preferences/ServerPreferencesController.java b/src/main/java/com/sparrowwallet/sparrow/preferences/ServerPreferencesController.java index bacce0ab..78579f9d 100644 --- a/src/main/java/com/sparrowwallet/sparrow/preferences/ServerPreferencesController.java +++ b/src/main/java/com/sparrowwallet/sparrow/preferences/ServerPreferencesController.java @@ -23,6 +23,7 @@ import javafx.stage.Stage; import javafx.util.Duration; import net.freehaven.tor.control.TorControlError; import org.berndpruenster.netlayer.tor.Tor; +import org.controlsfx.control.SegmentedButton; import org.controlsfx.glyphfont.Glyph; import org.controlsfx.validation.ValidationResult; import org.controlsfx.validation.ValidationSupport; @@ -41,6 +42,7 @@ import java.security.cert.CertificateFactory; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.List; +import java.util.Random; public class ServerPreferencesController extends PreferencesDetailController { private static final Logger log = LoggerFactory.getLogger(ServerPreferencesController.class); @@ -48,6 +50,27 @@ public class ServerPreferencesController extends PreferencesDetailController { @FXML private ToggleGroup serverTypeToggleGroup; + @FXML + private SegmentedButton serverTypeSegmentedButton; + + @FXML + private ToggleButton publicElectrumToggle; + + @FXML + private Form publicElectrumForm; + + @FXML + private ComboBox publicElectrumServer; + + @FXML + private UnlabeledToggleSwitch publicUseProxy; + + @FXML + private TextField publicProxyHost; + + @FXML + private TextField publicProxyPort; + @FXML private Form coreForm; @@ -135,13 +158,15 @@ public class ServerPreferencesController extends PreferencesDetailController { Platform.runLater(this::setupValidation); + publicElectrumForm.managedProperty().bind(publicElectrumForm.visibleProperty()); coreForm.managedProperty().bind(coreForm.visibleProperty()); electrumForm.managedProperty().bind(electrumForm.visibleProperty()); - coreForm.visibleProperty().bind(electrumForm.visibleProperty().not()); serverTypeToggleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> { if(serverTypeToggleGroup.getSelectedToggle() != null) { ServerType existingType = config.getServerType(); ServerType serverType = (ServerType)newValue.getUserData(); + publicElectrumForm.setVisible(serverType == ServerType.PUBLIC_ELECTRUM_SERVER); + coreForm.setVisible(serverType == ServerType.BITCOIN_CORE); electrumForm.setVisible(serverType == ServerType.ELECTRUM_SERVER); config.setServerType(serverType); testConnection.setGraphic(getGlyph(FontAwesome5.Glyph.QUESTION_CIRCLE, "")); @@ -153,12 +178,26 @@ public class ServerPreferencesController extends PreferencesDetailController { oldValue.setSelected(true); } }); - ServerType serverType = config.getServerType() != null ? config.getServerType() : (config.getCoreServer() == null && config.getElectrumServer() != null ? ServerType.ELECTRUM_SERVER : ServerType.BITCOIN_CORE); + ServerType serverType = config.getServerType() != null ? + (config.getServerType() == ServerType.PUBLIC_ELECTRUM_SERVER && Network.get() != Network.MAINNET ? ServerType.BITCOIN_CORE : config.getServerType()) : + (config.getCoreServer() == null && config.getElectrumServer() != null ? ServerType.ELECTRUM_SERVER : + (config.getCoreServer() != null || Network.get() != Network.MAINNET ? ServerType.BITCOIN_CORE : ServerType.PUBLIC_ELECTRUM_SERVER)); + if(Network.get() != Network.MAINNET) { + serverTypeSegmentedButton.getButtons().remove(publicElectrumToggle); + serverTypeToggleGroup.getToggles().remove(publicElectrumToggle); + } serverTypeToggleGroup.selectToggle(serverTypeToggleGroup.getToggles().stream().filter(toggle -> toggle.getUserData() == serverType).findFirst().orElse(null)); + publicElectrumServer.getSelectionModel().selectedItemProperty().addListener(getPublicElectrumServerListener(config)); + + publicUseProxy.selectedProperty().bindBidirectional(useProxy.selectedProperty()); + publicProxyHost.textProperty().bindBidirectional(proxyHost.textProperty()); + publicProxyPort.textProperty().bindBidirectional(proxyPort.textProperty()); + corePort.setTextFormatter(new TextFieldValidator(TextFieldValidator.ValidationModus.MAX_INTEGERS, 5).getFormatter()); electrumPort.setTextFormatter(new TextFieldValidator(TextFieldValidator.ValidationModus.MAX_INTEGERS, 5).getFormatter()); proxyPort.setTextFormatter(new TextFieldValidator(TextFieldValidator.ValidationModus.MAX_INTEGERS, 5).getFormatter()); + publicProxyPort.setTextFormatter(new TextFieldValidator(TextFieldValidator.ValidationModus.MAX_INTEGERS, 5).getFormatter()); coreHost.textProperty().addListener(getBitcoinCoreListener(config)); corePort.textProperty().addListener(getBitcoinCoreListener(config)); @@ -245,6 +284,8 @@ public class ServerPreferencesController extends PreferencesDetailController { proxyHost.setText(proxyHost.getText().trim()); proxyHost.setDisable(!newValue); proxyPort.setDisable(!newValue); + publicProxyHost.setDisable(!newValue); + publicProxyPort.setDisable(!newValue); }); boolean isConnected = AppServices.isConnecting() || AppServices.isConnected(); @@ -277,6 +318,13 @@ public class ServerPreferencesController extends PreferencesDetailController { testConnection.setVisible(true); }); + PublicElectrumServer configPublicElectrumServer = PublicElectrumServer.fromUrl(config.getPublicElectrumServer()); + if(configPublicElectrumServer == null) { + publicElectrumServer.setValue(PublicElectrumServer.values()[new Random().nextInt(PublicElectrumServer.values().length)]); + } else { + publicElectrumServer.setValue(configPublicElectrumServer); + } + String coreServer = config.getCoreServer(); if(coreServer != null) { Protocol protocol = Protocol.getProtocol(coreServer); @@ -340,6 +388,8 @@ public class ServerPreferencesController extends PreferencesDetailController { useProxy.setSelected(config.isUseProxy()); proxyHost.setDisable(!config.isUseProxy()); proxyPort.setDisable(!config.isUseProxy()); + publicProxyHost.setDisable(!config.isUseProxy()); + publicProxyPort.setDisable(!config.isUseProxy()); String proxyServer = config.getProxyServer(); if(proxyServer != null) { @@ -407,6 +457,11 @@ public class ServerPreferencesController extends PreferencesDetailController { private void setFieldsEditable(boolean editable) { serverTypeToggleGroup.getToggles().forEach(toggle -> ((ToggleButton)toggle).setDisable(!editable)); + publicElectrumServer.setDisable(!editable); + publicUseProxy.setDisable(!editable); + publicProxyHost.setDisable(!editable); + publicProxyPort.setDisable(!editable); + coreHost.setDisable(!editable); corePort.setDisable(!editable); coreAuthToggleGroup.getToggles().forEach(toggle -> ((ToggleButton)toggle).setDisable(!editable)); @@ -455,6 +510,15 @@ public class ServerPreferencesController extends PreferencesDetailController { } private void setupValidation() { + validationSupport.registerValidator(publicProxyHost, Validator.combine( + (Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Proxy host required", publicUseProxy.isSelected() && newValue.isEmpty()), + (Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid host name", getHost(newValue) == null) + )); + + validationSupport.registerValidator(publicProxyPort, Validator.combine( + (Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid proxy port", !newValue.isEmpty() && !isValidPort(Integer.parseInt(newValue))) + )); + validationSupport.registerValidator(coreHost, Validator.combine( (Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid Core host", getHost(newValue) == null) )); @@ -499,6 +563,13 @@ public class ServerPreferencesController extends PreferencesDetailController { validationSupport.setValidationDecorator(new StyleClassValidationDecoration()); } + @NotNull + private ChangeListener getPublicElectrumServerListener(Config config) { + return (observable, oldValue, newValue) -> { + config.setPublicElectrumServer(newValue.getUrl()); + }; + } + @NotNull private ChangeListener getBitcoinCoreListener(Config config) { return (observable, oldValue, newValue) -> { diff --git a/src/main/resources/com/sparrowwallet/sparrow/preferences/preferences.css b/src/main/resources/com/sparrowwallet/sparrow/preferences/preferences.css index 86d0a67d..9059f6b1 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/preferences/preferences.css +++ b/src/main/resources/com/sparrowwallet/sparrow/preferences/preferences.css @@ -31,4 +31,21 @@ .scroll-pane { -fx-background-color: transparent; -} \ No newline at end of file +} + +.public-warning { + -fx-text-fill: rgb(238, 210, 2); +} + +.public-electrum { + -fx-text-fill: linear-gradient(to bottom, derive(rgb(238, 210, 2), 30%), rgb(238, 210, 2)); +} + +.bitcoin-core { + -fx-text-fill: linear-gradient(to bottom, derive(#50a14f, 30%), #50a14f); +} + +.private-electrum { + -fx-text-fill: linear-gradient(to bottom, derive(#0b99c9, 30%), #0b99c9); +} + diff --git a/src/main/resources/com/sparrowwallet/sparrow/preferences/server.fxml b/src/main/resources/com/sparrowwallet/sparrow/preferences/server.fxml index 4acc281c..0e417c8b 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/preferences/server.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/preferences/server.fxml @@ -16,6 +16,9 @@ + + + @@ -30,17 +33,31 @@
- + + + + + + + + + + + + - + + + + @@ -51,6 +68,43 @@
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
@@ -98,7 +152,7 @@
-
+