mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-25 05:06:45 +00:00
initial commit of bwt integration
This commit is contained in:
parent
18bc7bf302
commit
a1c65cff75
34 changed files with 1230 additions and 115 deletions
|
@ -69,6 +69,7 @@ dependencies {
|
||||||
exclude group: 'org.openjfx', module: 'javafx-web'
|
exclude group: 'org.openjfx', module: 'javafx-web'
|
||||||
exclude group: 'org.openjfx', module: 'javafx-media'
|
exclude group: 'org.openjfx', module: 'javafx-media'
|
||||||
}
|
}
|
||||||
|
implementation('dev.bwt:bwt-jni:0.1.4')
|
||||||
testImplementation('junit:junit:4.12')
|
testImplementation('junit:junit:4.12')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
2
drongo
2
drongo
|
@ -1 +1 @@
|
||||||
Subproject commit 05674097428d25de043310f8ecddf06d998b3943
|
Subproject commit 6ad3f5373119b65d17b857738b8411ee88cea993
|
|
@ -521,16 +521,17 @@ public class AppController implements Initializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setServerToggleTooltip(Integer currentBlockHeight) {
|
private void setServerToggleTooltip(Integer currentBlockHeight) {
|
||||||
serverToggle.setTooltip(new Tooltip(AppServices.isOnline() ? "Connected to " + Config.get().getElectrumServer() + (currentBlockHeight != null ? " at height " + currentBlockHeight : "") : "Disconnected"));
|
serverToggle.setTooltip(new Tooltip(AppServices.isOnline() ? "Connected to " + Config.get().getServerAddress() + (currentBlockHeight != null ? " at height " + currentBlockHeight : "") : "Disconnected"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void newWallet(ActionEvent event) {
|
public void newWallet(ActionEvent event) {
|
||||||
WalletNameDialog dlg = new WalletNameDialog();
|
WalletNameDialog dlg = new WalletNameDialog();
|
||||||
Optional<String> walletName = dlg.showAndWait();
|
Optional<WalletNameDialog.NameAndBirthDate> optNameAndBirthDate = dlg.showAndWait();
|
||||||
if(walletName.isPresent()) {
|
if(optNameAndBirthDate.isPresent()) {
|
||||||
File walletFile = Storage.getWalletFile(walletName.get());
|
WalletNameDialog.NameAndBirthDate nameAndBirthDate = optNameAndBirthDate.get();
|
||||||
|
File walletFile = Storage.getWalletFile(nameAndBirthDate.getName());
|
||||||
Storage storage = new Storage(walletFile);
|
Storage storage = new Storage(walletFile);
|
||||||
Wallet wallet = new Wallet(walletName.get(), PolicyType.SINGLE, ScriptType.P2WPKH);
|
Wallet wallet = new Wallet(nameAndBirthDate.getName(), PolicyType.SINGLE, ScriptType.P2WPKH, nameAndBirthDate.getBirthDate());
|
||||||
addWalletTabOrWindow(storage, wallet, false);
|
addWalletTabOrWindow(storage, wallet, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -695,8 +696,17 @@ public class AppController implements Initializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addImportedWallet(Wallet wallet) {
|
private void addImportedWallet(Wallet wallet) {
|
||||||
File walletFile = Storage.getExistingWallet(wallet.getName());
|
WalletNameDialog nameDlg = new WalletNameDialog(wallet.getName());
|
||||||
|
Optional<WalletNameDialog.NameAndBirthDate> optNameAndBirthDate = nameDlg.showAndWait();
|
||||||
|
if(optNameAndBirthDate.isPresent()) {
|
||||||
|
WalletNameDialog.NameAndBirthDate nameAndBirthDate = optNameAndBirthDate.get();
|
||||||
|
wallet.setName(nameAndBirthDate.getName());
|
||||||
|
wallet.setBirthDate(nameAndBirthDate.getBirthDate());
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File walletFile = Storage.getExistingWallet(wallet.getName());
|
||||||
if(walletFile != null) {
|
if(walletFile != null) {
|
||||||
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
|
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
|
||||||
alert.setTitle("Existing wallet found");
|
alert.setTitle("Existing wallet found");
|
||||||
|
@ -1225,6 +1235,20 @@ public class AppController implements Initializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void bwtSyncStatus(BwtSyncStatusEvent event) {
|
||||||
|
if(AppServices.isOnline()) {
|
||||||
|
statusUpdated(new StatusEvent(event.getStatus()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void bwtScanStatus(BwtScanStatusEvent event) {
|
||||||
|
if(AppServices.isOnline()) {
|
||||||
|
statusUpdated(new StatusEvent(event.getStatus()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void newBlock(NewBlockEvent event) {
|
public void newBlock(NewBlockEvent event) {
|
||||||
setServerToggleTooltip(event.getHeight());
|
setServerToggleTooltip(event.getHeight());
|
||||||
|
|
|
@ -127,7 +127,7 @@ public class AppServices {
|
||||||
public void start() {
|
public void start() {
|
||||||
Config config = Config.get();
|
Config config = Config.get();
|
||||||
connectionService = createConnectionService();
|
connectionService = createConnectionService();
|
||||||
if(config.getMode() == Mode.ONLINE && config.getElectrumServer() != null && !config.getElectrumServer().isEmpty()) {
|
if(config.getMode() == Mode.ONLINE && config.getServerAddress() != null && !config.getServerAddress().isEmpty()) {
|
||||||
connectionService.start();
|
connectionService.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,6 +265,15 @@ public class AppServices {
|
||||||
return application;
|
return application;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<Wallet, Storage> getOpenWallets() {
|
||||||
|
Map<Wallet, Storage> openWallets = new LinkedHashMap<>();
|
||||||
|
for(Map<Wallet, Storage> walletStorageMap : walletWindows.values()) {
|
||||||
|
openWallets.putAll(walletStorageMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
return openWallets;
|
||||||
|
}
|
||||||
|
|
||||||
public Map<Wallet, Storage> getOpenWallets(Window window) {
|
public Map<Wallet, Storage> getOpenWallets(Window window) {
|
||||||
return walletWindows.get(window);
|
return walletWindows.get(window);
|
||||||
}
|
}
|
||||||
|
@ -365,7 +374,7 @@ public class AppServices {
|
||||||
addMempoolRateSizes(event.getMempoolRateSizes());
|
addMempoolRateSizes(event.getMempoolRateSizes());
|
||||||
minimumRelayFeeRate = event.getMinimumRelayFeeRate();
|
minimumRelayFeeRate = event.getMinimumRelayFeeRate();
|
||||||
String banner = event.getServerBanner();
|
String banner = event.getServerBanner();
|
||||||
String status = "Connected to " + Config.get().getElectrumServer() + " at height " + event.getBlockHeight();
|
String status = "Connected to " + Config.get().getServerAddress() + " at height " + event.getBlockHeight();
|
||||||
EventManager.get().post(new StatusEvent(status));
|
EventManager.get().post(new StatusEvent(status));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,15 @@ package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
import com.sparrowwallet.sparrow.AppServices;
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||||
|
import com.sparrowwallet.sparrow.io.Config;
|
||||||
import com.sparrowwallet.sparrow.io.Storage;
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
|
import com.sparrowwallet.sparrow.net.ServerType;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.BooleanBinding;
|
import javafx.beans.binding.BooleanBinding;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import org.controlsfx.control.textfield.CustomTextField;
|
import org.controlsfx.control.textfield.CustomTextField;
|
||||||
import org.controlsfx.control.textfield.TextFields;
|
import org.controlsfx.control.textfield.TextFields;
|
||||||
|
@ -16,28 +20,66 @@ import org.controlsfx.validation.ValidationSupport;
|
||||||
import org.controlsfx.validation.Validator;
|
import org.controlsfx.validation.Validator;
|
||||||
import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
|
import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
|
||||||
|
|
||||||
public class WalletNameDialog extends Dialog<String> {
|
import java.time.LocalDate;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class WalletNameDialog extends Dialog<WalletNameDialog.NameAndBirthDate> {
|
||||||
private final CustomTextField name;
|
private final CustomTextField name;
|
||||||
|
private final CheckBox existingCheck;
|
||||||
|
private final DatePicker existingPicker;
|
||||||
|
|
||||||
public WalletNameDialog() {
|
public WalletNameDialog() {
|
||||||
this.name = (CustomTextField)TextFields.createClearableTextField();
|
this("");
|
||||||
|
}
|
||||||
|
|
||||||
|
public WalletNameDialog(String initialName) {
|
||||||
final DialogPane dialogPane = getDialogPane();
|
final DialogPane dialogPane = getDialogPane();
|
||||||
AppServices.setStageIcon(dialogPane.getScene().getWindow());
|
AppServices.setStageIcon(dialogPane.getScene().getWindow());
|
||||||
|
boolean requestBirthDate = (Config.get().getServerType() == null || Config.get().getServerType() == ServerType.BITCOIN_CORE);
|
||||||
|
|
||||||
setTitle("Wallet Name");
|
setTitle("Wallet Name");
|
||||||
dialogPane.setHeaderText("Enter a name for this wallet:");
|
dialogPane.setHeaderText("Enter a name for this wallet:");
|
||||||
dialogPane.getStylesheets().add(AppServices.class.getResource("general.css").toExternalForm());
|
dialogPane.getStylesheets().add(AppServices.class.getResource("general.css").toExternalForm());
|
||||||
dialogPane.getButtonTypes().addAll(ButtonType.CANCEL);
|
dialogPane.getButtonTypes().addAll(ButtonType.CANCEL);
|
||||||
dialogPane.setPrefWidth(380);
|
dialogPane.setPrefWidth(420);
|
||||||
dialogPane.setPrefHeight(200);
|
dialogPane.setPrefHeight(requestBirthDate ? 250 : 200);
|
||||||
|
|
||||||
Glyph wallet = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.WALLET);
|
Glyph wallet = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.WALLET);
|
||||||
wallet.setFontSize(50);
|
wallet.setFontSize(50);
|
||||||
dialogPane.setGraphic(wallet);
|
dialogPane.setGraphic(wallet);
|
||||||
|
|
||||||
final VBox content = new VBox(10);
|
final VBox content = new VBox(20);
|
||||||
|
name = (CustomTextField)TextFields.createClearableTextField();
|
||||||
|
name.setText(initialName);
|
||||||
content.getChildren().add(name);
|
content.getChildren().add(name);
|
||||||
|
|
||||||
|
HBox existingBox = new HBox(10);
|
||||||
|
existingCheck = new CheckBox("Has existing transactions");
|
||||||
|
existingCheck.setPadding(new Insets(5, 0, 0, 0));
|
||||||
|
existingBox.getChildren().add(existingCheck);
|
||||||
|
|
||||||
|
existingPicker = new DatePicker();
|
||||||
|
existingPicker.setEditable(false);
|
||||||
|
existingPicker.setPrefWidth(130);
|
||||||
|
existingPicker.managedProperty().bind(existingPicker.visibleProperty());
|
||||||
|
existingPicker.setVisible(false);
|
||||||
|
existingBox.getChildren().add(existingPicker);
|
||||||
|
|
||||||
|
existingCheck.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
if(newValue) {
|
||||||
|
existingCheck.setText("Has existing transactions starting from");
|
||||||
|
existingPicker.setVisible(true);
|
||||||
|
} else {
|
||||||
|
existingCheck.setText("Has existing transactions");
|
||||||
|
existingPicker.setVisible(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(requestBirthDate) {
|
||||||
|
content.getChildren().add(existingBox);
|
||||||
|
}
|
||||||
|
|
||||||
dialogPane.setContent(content);
|
dialogPane.setContent(content);
|
||||||
|
|
||||||
ValidationSupport validationSupport = new ValidationSupport();
|
ValidationSupport validationSupport = new ValidationSupport();
|
||||||
|
@ -46,18 +88,39 @@ public class WalletNameDialog extends Dialog<String> {
|
||||||
Validator.createEmptyValidator("Wallet name is required"),
|
Validator.createEmptyValidator("Wallet name is required"),
|
||||||
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Wallet name is not unique", Storage.walletExists(newValue))
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Wallet name is not unique", Storage.walletExists(newValue))
|
||||||
));
|
));
|
||||||
|
validationSupport.registerValidator(existingPicker, Validator.combine(
|
||||||
|
(Control c, LocalDate newValue) -> ValidationResult.fromErrorIf( c, "Birth date not specified", existingCheck.isSelected() && newValue == null)
|
||||||
|
));
|
||||||
validationSupport.setValidationDecorator(new StyleClassValidationDecoration());
|
validationSupport.setValidationDecorator(new StyleClassValidationDecoration());
|
||||||
});
|
});
|
||||||
|
|
||||||
final ButtonType okButtonType = new javafx.scene.control.ButtonType("New Wallet", ButtonBar.ButtonData.OK_DONE);
|
final ButtonType okButtonType = new javafx.scene.control.ButtonType("Create Wallet", ButtonBar.ButtonData.OK_DONE);
|
||||||
dialogPane.getButtonTypes().addAll(okButtonType);
|
dialogPane.getButtonTypes().addAll(okButtonType);
|
||||||
Button okButton = (Button) dialogPane.lookupButton(okButtonType);
|
Button okButton = (Button) dialogPane.lookupButton(okButtonType);
|
||||||
BooleanBinding isInvalid = Bindings.createBooleanBinding(() ->
|
BooleanBinding isInvalid = Bindings.createBooleanBinding(() ->
|
||||||
name.getText().length() == 0 || Storage.walletExists(name.getText()), name.textProperty());
|
name.getText().length() == 0 || Storage.walletExists(name.getText()) || (existingCheck.isSelected() && existingPicker.getValue() == null), name.textProperty(), existingCheck.selectedProperty(), existingPicker.valueProperty());
|
||||||
okButton.disableProperty().bind(isInvalid);
|
okButton.disableProperty().bind(isInvalid);
|
||||||
|
|
||||||
name.setPromptText("Wallet Name");
|
name.setPromptText("Wallet Name");
|
||||||
Platform.runLater(name::requestFocus);
|
Platform.runLater(name::requestFocus);
|
||||||
setResultConverter(dialogButton -> dialogButton == okButtonType ? name.getText() : null);
|
setResultConverter(dialogButton -> dialogButton == okButtonType ? new NameAndBirthDate(name.getText(), existingPicker.getValue()) : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NameAndBirthDate {
|
||||||
|
private final String name;
|
||||||
|
private final Date birthDate;
|
||||||
|
|
||||||
|
public NameAndBirthDate(String name, LocalDate birthLocalDate) {
|
||||||
|
this.name = name;
|
||||||
|
this.birthDate = (birthLocalDate == null ? null : Date.from(birthLocalDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getBirthDate() {
|
||||||
|
return birthDate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.sparrowwallet.sparrow.event;
|
||||||
|
|
||||||
|
public class BwtElectrumReadyStatusEvent extends BwtStatusEvent {
|
||||||
|
private final String electrumAddr;
|
||||||
|
|
||||||
|
public BwtElectrumReadyStatusEvent(String status, String electrumAddr) {
|
||||||
|
super(status);
|
||||||
|
this.electrumAddr = electrumAddr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getElectrumAddr() {
|
||||||
|
return electrumAddr;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.sparrowwallet.sparrow.event;
|
||||||
|
|
||||||
|
public class BwtReadyStatusEvent extends BwtStatusEvent {
|
||||||
|
private final long shutdownPtr;
|
||||||
|
|
||||||
|
public BwtReadyStatusEvent(String status, long shutdownPtr) {
|
||||||
|
super(status);
|
||||||
|
this.shutdownPtr = shutdownPtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getShutdownPtr() {
|
||||||
|
return shutdownPtr;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package com.sparrowwallet.sparrow.event;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class BwtScanStatusEvent extends BwtStatusEvent {
|
||||||
|
private final int progress;
|
||||||
|
private final Date eta;
|
||||||
|
|
||||||
|
public BwtScanStatusEvent(String status, int progress, Date eta) {
|
||||||
|
super(status);
|
||||||
|
this.progress = progress;
|
||||||
|
this.eta = eta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getProgress() {
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getEta() {
|
||||||
|
return eta;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.sparrowwallet.sparrow.event;
|
||||||
|
|
||||||
|
public class BwtStatusEvent {
|
||||||
|
private final String status;
|
||||||
|
|
||||||
|
public BwtStatusEvent(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package com.sparrowwallet.sparrow.event;
|
||||||
|
|
||||||
|
public class BwtSyncStatusEvent extends BwtStatusEvent {
|
||||||
|
private final int progress;
|
||||||
|
private final int tip;
|
||||||
|
|
||||||
|
public BwtSyncStatusEvent(String status, int progress, int tip) {
|
||||||
|
super(status);
|
||||||
|
this.progress = progress;
|
||||||
|
this.tip = tip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getProgress() {
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTip() {
|
||||||
|
return tip;
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,7 @@ public class WalletHistoryStatusEvent {
|
||||||
this.errorMessage = null;
|
this.errorMessage = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public WalletHistoryStatusEvent(Wallet wallet,boolean loaded, String statusMessage) {
|
public WalletHistoryStatusEvent(Wallet wallet, boolean loaded, String statusMessage) {
|
||||||
this.wallet = wallet;
|
this.wallet = wallet;
|
||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
this.statusMessage = statusMessage;
|
this.statusMessage = statusMessage;
|
||||||
|
|
|
@ -4,8 +4,10 @@ import com.google.gson.*;
|
||||||
import com.sparrowwallet.drongo.BitcoinUnit;
|
import com.sparrowwallet.drongo.BitcoinUnit;
|
||||||
import com.sparrowwallet.sparrow.Mode;
|
import com.sparrowwallet.sparrow.Mode;
|
||||||
import com.sparrowwallet.sparrow.Theme;
|
import com.sparrowwallet.sparrow.Theme;
|
||||||
|
import com.sparrowwallet.sparrow.net.CoreAuthType;
|
||||||
import com.sparrowwallet.sparrow.net.ExchangeSource;
|
import com.sparrowwallet.sparrow.net.ExchangeSource;
|
||||||
import com.sparrowwallet.sparrow.net.FeeRatesSource;
|
import com.sparrowwallet.sparrow.net.FeeRatesSource;
|
||||||
|
import com.sparrowwallet.sparrow.net.ServerType;
|
||||||
import com.sparrowwallet.sparrow.wallet.FeeRatesSelection;
|
import com.sparrowwallet.sparrow.wallet.FeeRatesSelection;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -37,6 +39,12 @@ public class Config {
|
||||||
private List<File> recentWalletFiles;
|
private List<File> recentWalletFiles;
|
||||||
private Integer keyDerivationPeriod;
|
private Integer keyDerivationPeriod;
|
||||||
private File hwi;
|
private File hwi;
|
||||||
|
private ServerType serverType;
|
||||||
|
private String coreServer;
|
||||||
|
private CoreAuthType coreAuthType;
|
||||||
|
private File coreDataDir;
|
||||||
|
private String coreAuth;
|
||||||
|
private String coreWallet;
|
||||||
private String electrumServer;
|
private String electrumServer;
|
||||||
private File electrumServerCert;
|
private File electrumServerCert;
|
||||||
private boolean useProxy;
|
private boolean useProxy;
|
||||||
|
@ -241,6 +249,64 @@ public class Config {
|
||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ServerType getServerType() {
|
||||||
|
return serverType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServerType(ServerType serverType) {
|
||||||
|
this.serverType = serverType;
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServerAddress() {
|
||||||
|
return getServerType() == ServerType.BITCOIN_CORE ? getCoreServer() : getElectrumServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCoreServer() {
|
||||||
|
return coreServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCoreServer(String coreServer) {
|
||||||
|
this.coreServer = coreServer;
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CoreAuthType getCoreAuthType() {
|
||||||
|
return coreAuthType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCoreAuthType(CoreAuthType coreAuthType) {
|
||||||
|
this.coreAuthType = coreAuthType;
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getCoreDataDir() {
|
||||||
|
return coreDataDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCoreDataDir(File coreDataDir) {
|
||||||
|
this.coreDataDir = coreDataDir;
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCoreAuth() {
|
||||||
|
return coreAuth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCoreAuth(String coreAuth) {
|
||||||
|
this.coreAuth = coreAuth;
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCoreWallet() {
|
||||||
|
return coreWallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCoreWallet(String coreWallet) {
|
||||||
|
this.coreWallet = (coreWallet == null || coreWallet.isEmpty() ? null : coreWallet);
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
public String getElectrumServer() {
|
public String getElectrumServer() {
|
||||||
return electrumServer;
|
return electrumServer;
|
||||||
}
|
}
|
||||||
|
|
243
src/main/java/com/sparrowwallet/sparrow/net/Bwt.java
Normal file
243
src/main/java/com/sparrowwallet/sparrow/net/Bwt.java
Normal file
|
@ -0,0 +1,243 @@
|
||||||
|
package com.sparrowwallet.sparrow.net;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import com.sparrowwallet.drongo.KeyPurpose;
|
||||||
|
import com.sparrowwallet.drongo.Network;
|
||||||
|
import com.sparrowwallet.drongo.OutputDescriptor;
|
||||||
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
|
import com.sparrowwallet.sparrow.event.*;
|
||||||
|
import com.sparrowwallet.sparrow.io.Config;
|
||||||
|
import dev.bwt.daemon.CallbackNotifier;
|
||||||
|
import dev.bwt.daemon.NativeBwtDaemon;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.concurrent.Service;
|
||||||
|
import javafx.concurrent.Task;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Bwt {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(Bwt.class);
|
||||||
|
private Long shutdownPtr;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
org.controlsfx.tools.Platform platform = org.controlsfx.tools.Platform.getCurrent();
|
||||||
|
if(platform == org.controlsfx.tools.Platform.OSX) {
|
||||||
|
NativeUtils.loadLibraryFromJar("/native/osx/x64/libbwt.dylib");
|
||||||
|
} else if(platform == org.controlsfx.tools.Platform.WINDOWS) {
|
||||||
|
NativeUtils.loadLibraryFromJar("/native/windows/x64/bwt.dll");
|
||||||
|
} else {
|
||||||
|
NativeUtils.loadLibraryFromJar("/native/linux/x64/libbwt.so");
|
||||||
|
}
|
||||||
|
} catch(IOException e) {
|
||||||
|
log.error("Error loading bwt library", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void start(CallbackNotifier callback) {
|
||||||
|
List<String> descriptors = List.of("pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)");
|
||||||
|
Date now = new Date();
|
||||||
|
start(descriptors, (int)(now.getTime() / 1000), null, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void start(Collection<Wallet> wallets, CallbackNotifier callback) {
|
||||||
|
List<String> outputDescriptors = new ArrayList<>();
|
||||||
|
for(Wallet wallet : wallets) {
|
||||||
|
OutputDescriptor receiveOutputDescriptor = OutputDescriptor.getOutputDescriptor(wallet, KeyPurpose.RECEIVE);
|
||||||
|
outputDescriptors.add(receiveOutputDescriptor.toString(false, false));
|
||||||
|
OutputDescriptor changeOutputDescriptor = OutputDescriptor.getOutputDescriptor(wallet, KeyPurpose.CHANGE);
|
||||||
|
outputDescriptors.add(changeOutputDescriptor.toString(false, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
int rescanSince = wallets.stream().filter(wallet -> wallet.getBirthDate() != null).mapToInt(wallet -> (int)(wallet.getBirthDate().getTime() / 1000)).min().orElse(0);
|
||||||
|
int gapLimit = wallets.stream().filter(wallet -> wallet.getGapLimit() > 0).mapToInt(Wallet::getGapLimit).max().orElse(Wallet.DEFAULT_LOOKAHEAD);
|
||||||
|
|
||||||
|
start(outputDescriptors, rescanSince, gapLimit, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the bwt daemon with the provided wallets
|
||||||
|
* Blocks until the daemon is shut down.
|
||||||
|
*
|
||||||
|
* @param outputDescriptors descriptors of keys to add to Bitcoin Core
|
||||||
|
* @param rescanSince seconds since epoch to start scanning keys
|
||||||
|
* @param gapLimit desired gap limit beyond last used address
|
||||||
|
* @param callback object receiving notifications
|
||||||
|
*/
|
||||||
|
private void start(List<String> outputDescriptors, Integer rescanSince, Integer gapLimit, CallbackNotifier callback) {
|
||||||
|
BwtConfig bwtConfig = new BwtConfig();
|
||||||
|
bwtConfig.network = Network.get() == Network.MAINNET ? "bitcoin" : Network.get().getName();
|
||||||
|
bwtConfig.descriptors = outputDescriptors;
|
||||||
|
bwtConfig.rescanSince = rescanSince;
|
||||||
|
bwtConfig.gapLimit = gapLimit;
|
||||||
|
bwtConfig.verbose = log.isDebugEnabled() ? 2 : 0;
|
||||||
|
bwtConfig.electrumAddr = "127.0.0.1:0";
|
||||||
|
bwtConfig.electrumSkipMerkle = true;
|
||||||
|
|
||||||
|
Config config = Config.get();
|
||||||
|
bwtConfig.bitcoindUrl = config.getCoreServer();
|
||||||
|
if(config.getCoreAuthType() == CoreAuthType.COOKIE) {
|
||||||
|
bwtConfig.bitcoindDir = config.getCoreDataDir().getAbsolutePath() + "/";
|
||||||
|
} else {
|
||||||
|
bwtConfig.bitcoindAuth = config.getCoreAuth();
|
||||||
|
}
|
||||||
|
if(config.getCoreWallet() != null && !config.getCoreWallet().isEmpty()) {
|
||||||
|
bwtConfig.bitcoindWallet = config.getCoreWallet();
|
||||||
|
}
|
||||||
|
|
||||||
|
Gson gson = new Gson();
|
||||||
|
String jsonConfig = gson.toJson(bwtConfig);
|
||||||
|
|
||||||
|
NativeBwtDaemon.start(jsonConfig, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shut down the BWT daemon
|
||||||
|
*
|
||||||
|
* @param shutdownPtr the pointer provided on startup
|
||||||
|
*/
|
||||||
|
private void shutdown(long shutdownPtr) {
|
||||||
|
NativeBwtDaemon.shutdown(shutdownPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRunning() {
|
||||||
|
return shutdownPtr != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConnectionService getConnectionService(Collection<Wallet> wallets) {
|
||||||
|
return wallets != null ? new ConnectionService(wallets) : new ConnectionService();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DisconnectionService getDisconnectionService() {
|
||||||
|
return new DisconnectionService();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class BwtConfig {
|
||||||
|
@SerializedName("network")
|
||||||
|
public String network;
|
||||||
|
|
||||||
|
@SerializedName("bitcoind_url")
|
||||||
|
public String bitcoindUrl;
|
||||||
|
|
||||||
|
@SerializedName("bitcoind_auth")
|
||||||
|
public String bitcoindAuth;
|
||||||
|
|
||||||
|
@SerializedName("bitcoind_dir")
|
||||||
|
public String bitcoindDir;
|
||||||
|
|
||||||
|
@SerializedName("bitcoind_cookie")
|
||||||
|
public String bitcoindCookie;
|
||||||
|
|
||||||
|
@SerializedName("bitcoind_wallet")
|
||||||
|
public String bitcoindWallet;
|
||||||
|
|
||||||
|
@SerializedName("descriptors")
|
||||||
|
public List<String> descriptors;
|
||||||
|
|
||||||
|
@SerializedName("xpubs")
|
||||||
|
public String xpubs;
|
||||||
|
|
||||||
|
@SerializedName("rescan_since")
|
||||||
|
public Integer rescanSince;
|
||||||
|
|
||||||
|
@SerializedName("gap_limit")
|
||||||
|
public Integer gapLimit;
|
||||||
|
|
||||||
|
@SerializedName("verbose")
|
||||||
|
public Integer verbose;
|
||||||
|
|
||||||
|
@SerializedName("electrum_addr")
|
||||||
|
public String electrumAddr;
|
||||||
|
|
||||||
|
@SerializedName("electrum_skip_merkle")
|
||||||
|
public Boolean electrumSkipMerkle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class ConnectionService extends Service<Void> {
|
||||||
|
private final Collection<Wallet> wallets;
|
||||||
|
|
||||||
|
public ConnectionService() {
|
||||||
|
this.wallets = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConnectionService(Collection<Wallet> wallets) {
|
||||||
|
this.wallets = wallets;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Task<Void> createTask() {
|
||||||
|
return new Task<>() {
|
||||||
|
protected Void call() {
|
||||||
|
CallbackNotifier notifier = new CallbackNotifier() {
|
||||||
|
@Override
|
||||||
|
public void onBooting() {
|
||||||
|
Platform.runLater(() -> EventManager.get().post(new BwtStatusEvent("Starting bwt")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSyncProgress(float progress, int tip) {
|
||||||
|
int percent = (int) (progress * 100.0);
|
||||||
|
Platform.runLater(() -> EventManager.get().post(new BwtSyncStatusEvent("Syncing (" + percent + "%)", percent, tip)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScanProgress(float progress, int eta) {
|
||||||
|
int percent = (int) (progress * 100.0);
|
||||||
|
Date date = new Date((long) eta * 1000);
|
||||||
|
Platform.runLater(() -> EventManager.get().post(new BwtScanStatusEvent("Scanning (" + percent + "%)", percent, date)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onElectrumReady(String addr) {
|
||||||
|
Platform.runLater(() -> EventManager.get().post(new BwtElectrumReadyStatusEvent("Electrum server ready", addr)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHttpReady(String addr) {
|
||||||
|
log.info("http ready at " + addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReady(long shutdownPtr) {
|
||||||
|
Bwt.this.shutdownPtr = shutdownPtr;
|
||||||
|
Platform.runLater(() -> EventManager.get().post(new BwtReadyStatusEvent("Server ready", shutdownPtr)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if(wallets == null) {
|
||||||
|
Bwt.this.start(notifier);
|
||||||
|
} else {
|
||||||
|
Bwt.this.start(wallets, notifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class DisconnectionService extends Service<Void> {
|
||||||
|
@Override
|
||||||
|
protected Task<Void> createTask() {
|
||||||
|
return new Task<>() {
|
||||||
|
protected Void call() {
|
||||||
|
if(shutdownPtr == null) {
|
||||||
|
throw new IllegalStateException("Bwt has not been started");
|
||||||
|
}
|
||||||
|
|
||||||
|
Bwt.this.shutdown(shutdownPtr);
|
||||||
|
shutdownPtr = null;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.sparrowwallet.sparrow.net;
|
||||||
|
|
||||||
|
public enum CoreAuthType {
|
||||||
|
COOKIE, USERPASS;
|
||||||
|
}
|
|
@ -9,11 +9,11 @@ import com.sparrowwallet.drongo.Network;
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import com.sparrowwallet.drongo.Utils;
|
||||||
import com.sparrowwallet.drongo.protocol.*;
|
import com.sparrowwallet.drongo.protocol.*;
|
||||||
import com.sparrowwallet.drongo.wallet.*;
|
import com.sparrowwallet.drongo.wallet.*;
|
||||||
import com.sparrowwallet.sparrow.event.ConnectionEvent;
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
import com.sparrowwallet.sparrow.event.FeeRatesUpdatedEvent;
|
import com.sparrowwallet.sparrow.event.*;
|
||||||
import com.sparrowwallet.sparrow.event.TorStatusEvent;
|
|
||||||
import com.sparrowwallet.sparrow.io.Config;
|
import com.sparrowwallet.sparrow.io.Config;
|
||||||
import com.sparrowwallet.sparrow.wallet.SendController;
|
import com.sparrowwallet.sparrow.wallet.SendController;
|
||||||
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.IntegerProperty;
|
import javafx.beans.property.IntegerProperty;
|
||||||
import javafx.beans.property.SimpleIntegerProperty;
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
@ -26,6 +26,8 @@ import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.locks.Condition;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class ElectrumServer {
|
public class ElectrumServer {
|
||||||
|
@ -41,12 +43,25 @@ public class ElectrumServer {
|
||||||
|
|
||||||
private static ElectrumServerRpc electrumServerRpc = new SimpleElectrumServerRpc();
|
private static ElectrumServerRpc electrumServerRpc = new SimpleElectrumServerRpc();
|
||||||
|
|
||||||
|
private static String bwtElectrumServer;
|
||||||
|
|
||||||
private static synchronized Transport getTransport() throws ServerException {
|
private static synchronized Transport getTransport() throws ServerException {
|
||||||
if(transport == null) {
|
if(transport == null) {
|
||||||
try {
|
try {
|
||||||
String electrumServer = Config.get().getElectrumServer();
|
String electrumServer = null;
|
||||||
File electrumServerCert = Config.get().getElectrumServerCert();
|
File electrumServerCert = null;
|
||||||
String proxyServer = Config.get().getProxyServer();
|
String proxyServer = null;
|
||||||
|
|
||||||
|
if(Config.get().getServerType() == ServerType.BITCOIN_CORE) {
|
||||||
|
if(bwtElectrumServer == null) {
|
||||||
|
throw new ServerException("BWT server not started");
|
||||||
|
}
|
||||||
|
electrumServer = bwtElectrumServer;
|
||||||
|
} else if(Config.get().getServerType() == ServerType.ELECTRUM_SERVER) {
|
||||||
|
electrumServer = Config.get().getElectrumServer();
|
||||||
|
electrumServerCert = Config.get().getElectrumServerCert();
|
||||||
|
proxyServer = Config.get().getProxyServer();
|
||||||
|
}
|
||||||
|
|
||||||
if(electrumServer == null) {
|
if(electrumServer == null) {
|
||||||
throw new ServerException("Electrum server URL not specified");
|
throw new ServerException("Electrum server URL not specified");
|
||||||
|
@ -760,7 +775,10 @@ public class ElectrumServer {
|
||||||
private boolean firstCall = true;
|
private boolean firstCall = true;
|
||||||
private Thread reader;
|
private Thread reader;
|
||||||
private long feeRatesRetrievedAt;
|
private long feeRatesRetrievedAt;
|
||||||
private StringProperty statusProperty = new SimpleStringProperty();
|
private final Bwt bwt = new Bwt();
|
||||||
|
private final ReentrantLock bwtStartLock = new ReentrantLock();
|
||||||
|
private final Condition bwtStartCondition = bwtStartLock.newCondition();
|
||||||
|
private final StringProperty statusProperty = new SimpleStringProperty();
|
||||||
|
|
||||||
public ConnectionService() {
|
public ConnectionService() {
|
||||||
this(true);
|
this(true);
|
||||||
|
@ -775,6 +793,36 @@ public class ElectrumServer {
|
||||||
return new Task<>() {
|
return new Task<>() {
|
||||||
protected FeeRatesUpdatedEvent call() throws ServerException {
|
protected FeeRatesUpdatedEvent call() throws ServerException {
|
||||||
ElectrumServer electrumServer = new ElectrumServer();
|
ElectrumServer electrumServer = new ElectrumServer();
|
||||||
|
|
||||||
|
if(Config.get().getServerType() == ServerType.BITCOIN_CORE) {
|
||||||
|
if(!bwt.isRunning()) {
|
||||||
|
Bwt.ConnectionService bwtConnectionService = bwt.getConnectionService(subscribe ? AppServices.get().getOpenWallets().keySet() : null);
|
||||||
|
bwtConnectionService.setOnFailed(workerStateEvent -> {
|
||||||
|
log.error("Failed to start BWT", workerStateEvent.getSource().getException());
|
||||||
|
try {
|
||||||
|
bwtStartLock.lock();
|
||||||
|
bwtStartCondition.signal();
|
||||||
|
} finally {
|
||||||
|
bwtStartLock.unlock();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Platform.runLater(bwtConnectionService::start);
|
||||||
|
|
||||||
|
try {
|
||||||
|
bwtStartLock.lock();
|
||||||
|
bwtStartCondition.await();
|
||||||
|
|
||||||
|
if(!bwt.isRunning()) {
|
||||||
|
throw new ServerException("Check if Bitcoin Core is running, and the authentication details are correct.");
|
||||||
|
}
|
||||||
|
} catch(InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
} finally {
|
||||||
|
bwtStartLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(firstCall) {
|
if(firstCall) {
|
||||||
electrumServer.connect();
|
electrumServer.connect();
|
||||||
|
|
||||||
|
@ -839,6 +887,7 @@ public class ElectrumServer {
|
||||||
public void resetConnection() {
|
public void resetConnection() {
|
||||||
try {
|
try {
|
||||||
closeActiveConnection();
|
closeActiveConnection();
|
||||||
|
shutdownBwt();
|
||||||
firstCall = true;
|
firstCall = true;
|
||||||
} catch (ServerException e) {
|
} catch (ServerException e) {
|
||||||
log.error("Error closing connection during connection reset", e);
|
log.error("Error closing connection during connection reset", e);
|
||||||
|
@ -849,6 +898,7 @@ public class ElectrumServer {
|
||||||
public boolean cancel() {
|
public boolean cancel() {
|
||||||
try {
|
try {
|
||||||
closeActiveConnection();
|
closeActiveConnection();
|
||||||
|
shutdownBwt();
|
||||||
} catch (ServerException e) {
|
} catch (ServerException e) {
|
||||||
log.error("Error closing connection", e);
|
log.error("Error closing connection", e);
|
||||||
}
|
}
|
||||||
|
@ -856,6 +906,19 @@ public class ElectrumServer {
|
||||||
return super.cancel();
|
return super.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void shutdownBwt() {
|
||||||
|
if(bwt.isRunning()) {
|
||||||
|
Bwt.DisconnectionService disconnectionService = bwt.getDisconnectionService();
|
||||||
|
disconnectionService.setOnSucceeded(workerStateEvent -> {
|
||||||
|
ElectrumServer.bwtElectrumServer = null;
|
||||||
|
});
|
||||||
|
disconnectionService.setOnFailed(workerStateEvent -> {
|
||||||
|
log.error("Failed to stop BWT", workerStateEvent.getSource().getException());
|
||||||
|
});
|
||||||
|
Platform.runLater(disconnectionService::start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reset() {
|
public void reset() {
|
||||||
super.reset();
|
super.reset();
|
||||||
|
@ -872,6 +935,25 @@ public class ElectrumServer {
|
||||||
statusProperty.set(event.getStatus());
|
statusProperty.set(event.getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void bwtElectrumReadyStatus(BwtElectrumReadyStatusEvent event) {
|
||||||
|
if(this.isRunning()) {
|
||||||
|
ElectrumServer.bwtElectrumServer = Protocol.TCP.toUrlString(HostAndPort.fromString(event.getElectrumAddr()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void bwtReadyStatus(BwtReadyStatusEvent event) {
|
||||||
|
if(this.isRunning()) {
|
||||||
|
try {
|
||||||
|
bwtStartLock.lock();
|
||||||
|
bwtStartCondition.signal();
|
||||||
|
} finally {
|
||||||
|
bwtStartLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public StringProperty statusProperty() {
|
public StringProperty statusProperty() {
|
||||||
return statusProperty;
|
return statusProperty;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public enum FeeRatesSource {
|
public enum FeeRatesSource {
|
||||||
ELECTRUM_SERVER("Electrum Server") {
|
ELECTRUM_SERVER("Server") {
|
||||||
@Override
|
@Override
|
||||||
public Map<Integer, Double> getBlockTargetFeeRates(Map<Integer, Double> defaultblockTargetFeeRates) {
|
public Map<Integer, Double> getBlockTargetFeeRates(Map<Integer, Double> defaultblockTargetFeeRates) {
|
||||||
return Collections.emptyMap();
|
return Collections.emptyMap();
|
||||||
|
|
119
src/main/java/com/sparrowwallet/sparrow/net/NativeUtils.java
Normal file
119
src/main/java/com/sparrowwallet/sparrow/net/NativeUtils.java
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
package com.sparrowwallet.sparrow.net;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.file.FileSystemNotFoundException;
|
||||||
|
import java.nio.file.FileSystems;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.ProviderNotFoundException;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple library class which helps with loading dynamic libraries stored in the
|
||||||
|
* JAR archive. These libraries usually contain implementation of some methods in
|
||||||
|
* native code (using JNI - Java Native Interface).
|
||||||
|
*
|
||||||
|
* @see <a href="http://adamheinrich.com/blog/2012/how-to-load-native-jni-library-from-jar">http://adamheinrich.com/blog/2012/how-to-load-native-jni-library-from-jar</a>
|
||||||
|
* @see <a href="https://github.com/adamheinrich/native-utils">https://github.com/adamheinrich/native-utils</a>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class NativeUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The minimum length a prefix for a file has to have according to {@link File#createTempFile(String, String)}}.
|
||||||
|
*/
|
||||||
|
private static final int MIN_PREFIX_LENGTH = 3;
|
||||||
|
public static final String NATIVE_FOLDER_PATH_PREFIX = "nativeutils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporary directory which will contain the DLLs.
|
||||||
|
*/
|
||||||
|
private static File temporaryDir;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private constructor - this class will never be instanced
|
||||||
|
*/
|
||||||
|
private NativeUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads library from current JAR archive
|
||||||
|
*
|
||||||
|
* The file from JAR is copied into system temporary directory and then loaded. The temporary file is deleted after
|
||||||
|
* exiting.
|
||||||
|
* Method uses String as filename because the pathname is "abstract", not system-dependent.
|
||||||
|
*
|
||||||
|
* @param path The path of file inside JAR as absolute path (beginning with '/'), e.g. /package/File.ext
|
||||||
|
* @throws IOException If temporary file creation or read/write operation fails
|
||||||
|
* @throws IllegalArgumentException If source file (param path) does not exist
|
||||||
|
* @throws IllegalArgumentException If the path is not absolute or if the filename is shorter than three characters
|
||||||
|
* (restriction of {@link File#createTempFile(java.lang.String, java.lang.String)}).
|
||||||
|
* @throws FileNotFoundException If the file could not be found inside the JAR.
|
||||||
|
*/
|
||||||
|
public static void loadLibraryFromJar(String path) throws IOException {
|
||||||
|
|
||||||
|
if (null == path || !path.startsWith("/")) {
|
||||||
|
throw new IllegalArgumentException("The path has to be absolute (start with '/').");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtain filename from path
|
||||||
|
String[] parts = path.split("/");
|
||||||
|
String filename = (parts.length > 1) ? parts[parts.length - 1] : null;
|
||||||
|
|
||||||
|
// Check if the filename is okay
|
||||||
|
if (filename == null || filename.length() < MIN_PREFIX_LENGTH) {
|
||||||
|
throw new IllegalArgumentException("The filename has to be at least 3 characters long.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare temporary file
|
||||||
|
if (temporaryDir == null) {
|
||||||
|
temporaryDir = createTempDirectory(NATIVE_FOLDER_PATH_PREFIX);
|
||||||
|
temporaryDir.deleteOnExit();
|
||||||
|
}
|
||||||
|
|
||||||
|
File temp = new File(temporaryDir, filename);
|
||||||
|
|
||||||
|
try (InputStream is = NativeUtils.class.getResourceAsStream(path)) {
|
||||||
|
Files.copy(is, temp.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
} catch (IOException e) {
|
||||||
|
temp.delete();
|
||||||
|
throw e;
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
temp.delete();
|
||||||
|
throw new FileNotFoundException("File " + path + " was not found inside JAR.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
System.load(temp.getAbsolutePath());
|
||||||
|
} finally {
|
||||||
|
if (isPosixCompliant()) {
|
||||||
|
// Assume POSIX compliant file system, can be deleted after loading
|
||||||
|
temp.delete();
|
||||||
|
} else {
|
||||||
|
// Assume non-POSIX, and don't delete until last file descriptor closed
|
||||||
|
temp.deleteOnExit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isPosixCompliant() {
|
||||||
|
try {
|
||||||
|
return FileSystems.getDefault()
|
||||||
|
.supportedFileAttributeViews()
|
||||||
|
.contains("posix");
|
||||||
|
} catch (FileSystemNotFoundException
|
||||||
|
| ProviderNotFoundException
|
||||||
|
| SecurityException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File createTempDirectory(String prefix) throws IOException {
|
||||||
|
String tempDir = System.getProperty("java.io.tmpdir");
|
||||||
|
File generatedDir = new File(tempDir, prefix + System.nanoTime());
|
||||||
|
|
||||||
|
if (!generatedDir.mkdir())
|
||||||
|
throw new IOException("Failed to create temp directory " + generatedDir.getName());
|
||||||
|
|
||||||
|
return generatedDir;
|
||||||
|
}
|
||||||
|
}
|
|
@ -64,6 +64,27 @@ public enum Protocol {
|
||||||
public Transport getTransport(HostAndPort server, File serverCert, HostAndPort proxy) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
|
public Transport getTransport(HostAndPort server, File serverCert, HostAndPort proxy) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
|
||||||
return new ProxyTcpOverTlsTransport(server, serverCert, proxy);
|
return new ProxyTcpOverTlsTransport(server, serverCert, proxy);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
HTTP {
|
||||||
|
@Override
|
||||||
|
public Transport getTransport(HostAndPort server) throws KeyManagementException, NoSuchAlgorithmException {
|
||||||
|
throw new UnsupportedOperationException("No transport supported for HTTP");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Transport getTransport(HostAndPort server, File serverCert) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
|
||||||
|
throw new UnsupportedOperationException("No transport supported for HTTP");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Transport getTransport(HostAndPort server, HostAndPort proxy) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
|
||||||
|
throw new UnsupportedOperationException("No transport supported for HTTP");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Transport getTransport(HostAndPort server, File serverCert, HostAndPort proxy) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
|
||||||
|
throw new UnsupportedOperationException("No transport supported for HTTP");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public abstract Transport getTransport(HostAndPort server) throws KeyManagementException, NoSuchAlgorithmException;
|
public abstract Transport getTransport(HostAndPort server) throws KeyManagementException, NoSuchAlgorithmException;
|
||||||
|
@ -105,6 +126,9 @@ public enum Protocol {
|
||||||
if(url.startsWith("ssl://")) {
|
if(url.startsWith("ssl://")) {
|
||||||
return SSL;
|
return SSL;
|
||||||
}
|
}
|
||||||
|
if(url.startsWith("http://")) {
|
||||||
|
return HTTP;
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
15
src/main/java/com/sparrowwallet/sparrow/net/ServerType.java
Normal file
15
src/main/java/com/sparrowwallet/sparrow/net/ServerType.java
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package com.sparrowwallet.sparrow.net;
|
||||||
|
|
||||||
|
public enum ServerType {
|
||||||
|
BITCOIN_CORE("Bitcoin Core"), ELECTRUM_SERVER("Electrum Server");
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
ServerType(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
|
@ -149,7 +149,7 @@ public class TcpTransport implements Transport, Closeable {
|
||||||
//Restore interrupt status and continue
|
//Restore interrupt status and continue
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
log.debug("Connection error while reading", e);
|
log.trace("Connection error while reading", e);
|
||||||
if(running) {
|
if(running) {
|
||||||
lastException = e;
|
lastException = e;
|
||||||
reading = false;
|
reading = false;
|
||||||
|
@ -177,7 +177,7 @@ public class TcpTransport implements Transport, Closeable {
|
||||||
String response = in.readLine();
|
String response = in.readLine();
|
||||||
|
|
||||||
if(response == null) {
|
if(response == null) {
|
||||||
throw new IOException("Could not connect to server at " + Config.get().getElectrumServer());
|
throw new IOException("Could not connect to server at " + Config.get().getServerAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
|
|
|
@ -3,6 +3,8 @@ package com.sparrowwallet.sparrow.preferences;
|
||||||
import com.sparrowwallet.sparrow.AppServices;
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
import com.sparrowwallet.sparrow.io.Config;
|
import com.sparrowwallet.sparrow.io.Config;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
import javafx.fxml.Initializable;
|
import javafx.fxml.Initializable;
|
||||||
|
@ -24,6 +26,8 @@ public class PreferencesController implements Initializable {
|
||||||
@FXML
|
@FXML
|
||||||
private StackPane preferencesPane;
|
private StackPane preferencesPane;
|
||||||
|
|
||||||
|
private final BooleanProperty closing = new SimpleBooleanProperty(false);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize(URL location, ResourceBundle resources) {
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
|
|
||||||
|
@ -56,6 +60,10 @@ public class PreferencesController implements Initializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BooleanProperty closingProperty() {
|
||||||
|
return closing;
|
||||||
|
}
|
||||||
|
|
||||||
FXMLLoader setPreferencePane(String fxmlName) {
|
FXMLLoader setPreferencePane(String fxmlName) {
|
||||||
preferencesPane.getChildren().removeAll(preferencesPane.getChildren());
|
preferencesPane.getChildren().removeAll(preferencesPane.getChildren());
|
||||||
|
|
||||||
|
|
|
@ -49,10 +49,11 @@ public class PreferencesDialog extends Dialog<Boolean> {
|
||||||
}
|
}
|
||||||
|
|
||||||
dialogPane.setPrefWidth(650);
|
dialogPane.setPrefWidth(650);
|
||||||
dialogPane.setPrefHeight(550);
|
dialogPane.setPrefHeight(600);
|
||||||
|
|
||||||
existingConnection = ElectrumServer.isConnected();
|
existingConnection = ElectrumServer.isConnected();
|
||||||
setOnCloseRequest(event -> {
|
setOnCloseRequest(event -> {
|
||||||
|
preferencesController.closingProperty().set(true);
|
||||||
if(existingConnection && !ElectrumServer.isConnected()) {
|
if(existingConnection && !ElectrumServer.isConnected()) {
|
||||||
EventManager.get().post(new RequestConnectEvent());
|
EventManager.get().post(new RequestConnectEvent());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
package com.sparrowwallet.sparrow.preferences;
|
package com.sparrowwallet.sparrow.preferences;
|
||||||
|
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
import com.google.common.net.HostAndPort;
|
import com.google.common.net.HostAndPort;
|
||||||
|
import com.sparrowwallet.drongo.Network;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.control.TextFieldValidator;
|
import com.sparrowwallet.sparrow.control.TextFieldValidator;
|
||||||
import com.sparrowwallet.sparrow.control.UnlabeledToggleSwitch;
|
import com.sparrowwallet.sparrow.control.UnlabeledToggleSwitch;
|
||||||
|
import com.sparrowwallet.sparrow.event.BwtStatusEvent;
|
||||||
import com.sparrowwallet.sparrow.event.ConnectionEvent;
|
import com.sparrowwallet.sparrow.event.ConnectionEvent;
|
||||||
import com.sparrowwallet.sparrow.event.RequestDisconnectEvent;
|
import com.sparrowwallet.sparrow.event.RequestDisconnectEvent;
|
||||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||||
import com.sparrowwallet.sparrow.io.Config;
|
import com.sparrowwallet.sparrow.io.Config;
|
||||||
import com.sparrowwallet.sparrow.net.ElectrumServer;
|
import com.sparrowwallet.sparrow.net.*;
|
||||||
import com.sparrowwallet.sparrow.net.Protocol;
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
import javafx.concurrent.WorkerStateEvent;
|
import javafx.concurrent.WorkerStateEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.control.Control;
|
import javafx.scene.text.Font;
|
||||||
import javafx.scene.control.TextArea;
|
import javafx.stage.DirectoryChooser;
|
||||||
import javafx.scene.control.TextField;
|
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
|
@ -30,6 +30,8 @@ import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import tornadofx.control.Field;
|
||||||
|
import tornadofx.control.Form;
|
||||||
|
|
||||||
import javax.net.ssl.SSLHandshakeException;
|
import javax.net.ssl.SSLHandshakeException;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -41,19 +43,61 @@ public class ServerPreferencesController extends PreferencesDetailController {
|
||||||
private static final Logger log = LoggerFactory.getLogger(ServerPreferencesController.class);
|
private static final Logger log = LoggerFactory.getLogger(ServerPreferencesController.class);
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TextField host;
|
private ToggleGroup serverTypeToggleGroup;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TextField port;
|
private Form coreForm;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private UnlabeledToggleSwitch useSsl;
|
private TextField coreHost;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TextField certificate;
|
private TextField corePort;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button certificateSelect;
|
private ToggleGroup coreAuthToggleGroup;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Field coreDataDirField;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TextField coreDataDir;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button coreDataDirSelect;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Field coreUserPassField;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TextField coreUser;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private PasswordField corePass;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private UnlabeledToggleSwitch coreMultiWallet;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TextField coreWallet;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Form electrumForm;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TextField electrumHost;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TextField electrumPort;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private UnlabeledToggleSwitch electrumUseSsl;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TextField electrumCertificate;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button electrumCertificateSelect;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private UnlabeledToggleSwitch useProxy;
|
private UnlabeledToggleSwitch useProxy;
|
||||||
|
@ -77,33 +121,100 @@ public class ServerPreferencesController extends PreferencesDetailController {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initializeView(Config config) {
|
public void initializeView(Config config) {
|
||||||
|
EventManager.get().register(this);
|
||||||
|
getMasterController().closingProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
EventManager.get().unregister(this);
|
||||||
|
});
|
||||||
|
|
||||||
Platform.runLater(this::setupValidation);
|
Platform.runLater(this::setupValidation);
|
||||||
|
|
||||||
port.setTextFormatter(new TextFieldValidator(TextFieldValidator.ValidationModus.MAX_INTEGERS, 5).getFormatter());
|
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 serverType = (ServerType)newValue.getUserData();
|
||||||
|
electrumForm.setVisible(serverType == ServerType.ELECTRUM_SERVER);
|
||||||
|
config.setServerType(serverType);
|
||||||
|
testConnection.setGraphic(getGlyph(FontAwesome5.Glyph.QUESTION_CIRCLE, ""));
|
||||||
|
testResults.clear();
|
||||||
|
} else if(oldValue != null) {
|
||||||
|
oldValue.setSelected(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ServerType serverType = config.getServerType() != null ? config.getServerType() : (config.getCoreServer() == null && config.getElectrumServer() != null ? ServerType.ELECTRUM_SERVER : ServerType.BITCOIN_CORE);
|
||||||
|
serverTypeToggleGroup.selectToggle(serverTypeToggleGroup.getToggles().stream().filter(toggle -> toggle.getUserData() == serverType).findFirst().orElse(null));
|
||||||
|
|
||||||
|
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());
|
proxyPort.setTextFormatter(new TextFieldValidator(TextFieldValidator.ValidationModus.MAX_INTEGERS, 5).getFormatter());
|
||||||
|
|
||||||
host.textProperty().addListener(getElectrumServerListener(config));
|
coreHost.textProperty().addListener(getBitcoinCoreListener(config));
|
||||||
port.textProperty().addListener(getElectrumServerListener(config));
|
corePort.textProperty().addListener(getBitcoinCoreListener(config));
|
||||||
|
|
||||||
|
coreUser.textProperty().addListener(getBitcoinAuthListener(config));
|
||||||
|
corePass.textProperty().addListener(getBitcoinAuthListener(config));
|
||||||
|
|
||||||
|
coreWallet.textProperty().addListener(getBitcoinWalletListener(config));
|
||||||
|
|
||||||
|
electrumHost.textProperty().addListener(getElectrumServerListener(config));
|
||||||
|
electrumPort.textProperty().addListener(getElectrumServerListener(config));
|
||||||
|
|
||||||
proxyHost.textProperty().addListener(getProxyListener(config));
|
proxyHost.textProperty().addListener(getProxyListener(config));
|
||||||
proxyPort.textProperty().addListener(getProxyListener(config));
|
proxyPort.textProperty().addListener(getProxyListener(config));
|
||||||
|
|
||||||
useSsl.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
coreDataDirField.managedProperty().bind(coreDataDirField.visibleProperty());
|
||||||
setElectrumServerInConfig(config);
|
coreUserPassField.managedProperty().bind(coreUserPassField.visibleProperty());
|
||||||
certificate.setDisable(!newValue);
|
coreUserPassField.visibleProperty().bind(coreDataDirField.visibleProperty().not());
|
||||||
certificateSelect.setDisable(!newValue);
|
coreAuthToggleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
if(coreAuthToggleGroup.getSelectedToggle() != null) {
|
||||||
|
CoreAuthType coreAuthType = (CoreAuthType)newValue.getUserData();
|
||||||
|
coreDataDirField.setVisible(coreAuthType == CoreAuthType.COOKIE);
|
||||||
|
config.setCoreAuthType(coreAuthType);
|
||||||
|
} else if(oldValue != null) {
|
||||||
|
oldValue.setSelected(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
CoreAuthType coreAuthType = config.getCoreAuthType() != null ? config.getCoreAuthType() : CoreAuthType.COOKIE;
|
||||||
|
coreAuthToggleGroup.selectToggle(coreAuthToggleGroup.getToggles().stream().filter(toggle -> toggle.getUserData() == coreAuthType).findFirst().orElse(null));
|
||||||
|
|
||||||
|
coreDataDir.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
File dataDir = getDirectory(newValue);
|
||||||
|
config.setCoreDataDir(dataDir);
|
||||||
});
|
});
|
||||||
|
|
||||||
certificate.textProperty().addListener((observable, oldValue, newValue) -> {
|
coreDataDirSelect.setOnAction(event -> {
|
||||||
File crtFile = getCertificate(newValue);
|
Stage window = new Stage();
|
||||||
if(crtFile != null) {
|
|
||||||
config.setElectrumServerCert(crtFile);
|
DirectoryChooser directorChooser = new DirectoryChooser();
|
||||||
} else {
|
directorChooser.setTitle("Select Bitcoin Core Data Directory");
|
||||||
config.setElectrumServerCert(null);
|
directorChooser.setInitialDirectory(config.getCoreDataDir() != null ? config.getCoreDataDir() : new File(System.getProperty("user.home")));
|
||||||
|
|
||||||
|
File dataDir = directorChooser.showDialog(window);
|
||||||
|
if(dataDir != null) {
|
||||||
|
coreDataDir.setText(dataDir.getAbsolutePath());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
certificateSelect.setOnAction(event -> {
|
coreMultiWallet.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
coreWallet.setText(" ");
|
||||||
|
coreWallet.setText("");
|
||||||
|
coreWallet.setDisable(!newValue);
|
||||||
|
coreWallet.setPromptText(newValue ? "" : "Default");
|
||||||
|
});
|
||||||
|
|
||||||
|
electrumUseSsl.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
setElectrumServerInConfig(config);
|
||||||
|
electrumCertificate.setDisable(!newValue);
|
||||||
|
electrumCertificateSelect.setDisable(!newValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
electrumCertificate.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
File crtFile = getCertificate(newValue);
|
||||||
|
config.setElectrumServerCert(crtFile);
|
||||||
|
});
|
||||||
|
|
||||||
|
electrumCertificateSelect.setOnAction(event -> {
|
||||||
Stage window = new Stage();
|
Stage window = new Stage();
|
||||||
|
|
||||||
FileChooser fileChooser = new FileChooser();
|
FileChooser fileChooser = new FileChooser();
|
||||||
|
@ -115,7 +226,7 @@ public class ServerPreferencesController extends PreferencesDetailController {
|
||||||
|
|
||||||
File file = fileChooser.showOpenDialog(window);
|
File file = fileChooser.showOpenDialog(window);
|
||||||
if(file != null) {
|
if(file != null) {
|
||||||
certificate.setText(file.getAbsolutePath());
|
electrumCertificate.setText(file.getAbsolutePath());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -127,10 +238,10 @@ public class ServerPreferencesController extends PreferencesDetailController {
|
||||||
proxyPort.setDisable(!newValue);
|
proxyPort.setDisable(!newValue);
|
||||||
|
|
||||||
if(newValue) {
|
if(newValue) {
|
||||||
useSsl.setSelected(true);
|
electrumUseSsl.setSelected(true);
|
||||||
useSsl.setDisable(true);
|
electrumUseSsl.setDisable(true);
|
||||||
} else {
|
} else {
|
||||||
useSsl.setDisable(false);
|
electrumUseSsl.setDisable(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -139,29 +250,11 @@ public class ServerPreferencesController extends PreferencesDetailController {
|
||||||
|
|
||||||
testConnection.managedProperty().bind(testConnection.visibleProperty());
|
testConnection.managedProperty().bind(testConnection.visibleProperty());
|
||||||
testConnection.setVisible(!isConnected);
|
testConnection.setVisible(!isConnected);
|
||||||
|
setTestResultsFont();
|
||||||
testConnection.setOnAction(event -> {
|
testConnection.setOnAction(event -> {
|
||||||
testResults.setText("Connecting to " + config.getElectrumServer() + "...");
|
|
||||||
testConnection.setGraphic(getGlyph(FontAwesome5.Glyph.ELLIPSIS_H, null));
|
testConnection.setGraphic(getGlyph(FontAwesome5.Glyph.ELLIPSIS_H, null));
|
||||||
|
testResults.setText("Connecting to " + config.getServerAddress() + "...");
|
||||||
ElectrumServer.ConnectionService connectionService = new ElectrumServer.ConnectionService(false);
|
startElectrumConnection();
|
||||||
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();
|
|
||||||
});
|
|
||||||
connectionService.start();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
editConnection.managedProperty().bind(editConnection.visibleProperty());
|
editConnection.managedProperty().bind(editConnection.visibleProperty());
|
||||||
|
@ -173,27 +266,61 @@ public class ServerPreferencesController extends PreferencesDetailController {
|
||||||
testConnection.setVisible(true);
|
testConnection.setVisible(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
String coreServer = config.getCoreServer();
|
||||||
|
if(coreServer != null) {
|
||||||
|
Protocol protocol = Protocol.getProtocol(coreServer);
|
||||||
|
|
||||||
|
if(protocol != null) {
|
||||||
|
HostAndPort server = protocol.getServerHostAndPort(coreServer);
|
||||||
|
coreHost.setText(server.getHost());
|
||||||
|
if(server.hasPort()) {
|
||||||
|
corePort.setText(Integer.toString(server.getPort()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
coreHost.setText("127.0.0.1");
|
||||||
|
corePort.setText(String.valueOf(Network.get().getDefaultPort()));
|
||||||
|
}
|
||||||
|
|
||||||
|
coreDataDir.setText(config.getCoreDataDir() != null ? config.getCoreDataDir().getAbsolutePath() : getDefaultCoreDataDir().getAbsolutePath());
|
||||||
|
|
||||||
|
if(config.getCoreAuth() != null) {
|
||||||
|
String[] userPass = config.getCoreAuth().split(":");
|
||||||
|
if(userPass.length > 0) {
|
||||||
|
coreUser.setText(userPass[0]);
|
||||||
|
}
|
||||||
|
if(userPass.length > 1) {
|
||||||
|
corePass.setText(userPass[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
coreMultiWallet.setSelected(true);
|
||||||
|
coreMultiWallet.setSelected(config.getCoreWallet() != null);
|
||||||
|
if(config.getCoreWallet() != null) {
|
||||||
|
coreWallet.setText(config.getCoreWallet());
|
||||||
|
}
|
||||||
|
|
||||||
String electrumServer = config.getElectrumServer();
|
String electrumServer = config.getElectrumServer();
|
||||||
if(electrumServer != null) {
|
if(electrumServer != null) {
|
||||||
Protocol protocol = Protocol.getProtocol(electrumServer);
|
Protocol protocol = Protocol.getProtocol(electrumServer);
|
||||||
|
|
||||||
if(protocol != null) {
|
if(protocol != null) {
|
||||||
boolean ssl = protocol.equals(Protocol.SSL);
|
boolean ssl = protocol.equals(Protocol.SSL);
|
||||||
useSsl.setSelected(ssl);
|
electrumUseSsl.setSelected(ssl);
|
||||||
certificate.setDisable(!ssl);
|
electrumCertificate.setDisable(!ssl);
|
||||||
certificateSelect.setDisable(!ssl);
|
electrumCertificateSelect.setDisable(!ssl);
|
||||||
|
|
||||||
HostAndPort server = protocol.getServerHostAndPort(electrumServer);
|
HostAndPort server = protocol.getServerHostAndPort(electrumServer);
|
||||||
host.setText(server.getHost());
|
electrumHost.setText(server.getHost());
|
||||||
if(server.hasPort()) {
|
if(server.hasPort()) {
|
||||||
port.setText(Integer.toString(server.getPort()));
|
electrumPort.setText(Integer.toString(server.getPort()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
File certificateFile = config.getElectrumServerCert();
|
File certificateFile = config.getElectrumServerCert();
|
||||||
if(certificateFile != null) {
|
if(certificateFile != null) {
|
||||||
certificate.setText(certificateFile.getAbsolutePath());
|
electrumCertificate.setText(certificateFile.getAbsolutePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
useProxy.setSelected(config.isUseProxy());
|
useProxy.setSelected(config.isUseProxy());
|
||||||
|
@ -201,8 +328,8 @@ public class ServerPreferencesController extends PreferencesDetailController {
|
||||||
proxyPort.setDisable(!config.isUseProxy());
|
proxyPort.setDisable(!config.isUseProxy());
|
||||||
|
|
||||||
if(config.isUseProxy()) {
|
if(config.isUseProxy()) {
|
||||||
useSsl.setSelected(true);
|
electrumUseSsl.setSelected(true);
|
||||||
useSsl.setDisable(true);
|
electrumUseSsl.setDisable(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
String proxyServer = config.getProxyServer();
|
String proxyServer = config.getProxyServer();
|
||||||
|
@ -215,12 +342,46 @@ public class ServerPreferencesController extends PreferencesDetailController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void startElectrumConnection() {
|
||||||
|
ElectrumServer.ConnectionService connectionService = new ElectrumServer.ConnectionService(false);
|
||||||
|
connectionService.setPeriod(Duration.hours(1));
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
connectionService.start();
|
||||||
|
}
|
||||||
|
|
||||||
private void setFieldsEditable(boolean editable) {
|
private void setFieldsEditable(boolean editable) {
|
||||||
host.setEditable(editable);
|
coreHost.setEditable(editable);
|
||||||
port.setEditable(editable);
|
corePort.setEditable(editable);
|
||||||
useSsl.setDisable(!editable);
|
for(Toggle toggle : coreAuthToggleGroup.getToggles()) {
|
||||||
certificate.setEditable(editable);
|
((ToggleButton)toggle).setDisable(!editable);
|
||||||
certificateSelect.setDisable(!editable);
|
}
|
||||||
|
coreDataDir.setEditable(editable);
|
||||||
|
coreDataDirSelect.setDisable(!editable);
|
||||||
|
coreUser.setEditable(editable);
|
||||||
|
corePass.setEditable(editable);
|
||||||
|
coreMultiWallet.setDisable(!editable);
|
||||||
|
coreWallet.setEditable(editable);
|
||||||
|
|
||||||
|
electrumHost.setEditable(editable);
|
||||||
|
electrumPort.setEditable(editable);
|
||||||
|
electrumUseSsl.setDisable(!editable);
|
||||||
|
electrumCertificate.setEditable(editable);
|
||||||
|
electrumCertificateSelect.setDisable(!editable);
|
||||||
useProxy.setDisable(!editable);
|
useProxy.setDisable(!editable);
|
||||||
proxyHost.setEditable(editable);
|
proxyHost.setEditable(editable);
|
||||||
proxyPort.setEditable(editable);
|
proxyPort.setEditable(editable);
|
||||||
|
@ -252,12 +413,36 @@ public class ServerPreferencesController extends PreferencesDetailController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupValidation() {
|
private void setupValidation() {
|
||||||
validationSupport.registerValidator(host, Validator.combine(
|
validationSupport.registerValidator(coreHost, Validator.combine(
|
||||||
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid host name", getHost(newValue) == null)
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid Core host", getHost(newValue) == null)
|
||||||
));
|
));
|
||||||
|
|
||||||
validationSupport.registerValidator(port, Validator.combine(
|
validationSupport.registerValidator(corePort, Validator.combine(
|
||||||
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid port", !newValue.isEmpty() && !isValidPort(Integer.parseInt(newValue)))
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid Core port", !newValue.isEmpty() && !isValidPort(Integer.parseInt(newValue)))
|
||||||
|
));
|
||||||
|
|
||||||
|
validationSupport.registerValidator(coreDataDir, Validator.combine(
|
||||||
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Core Data Dir required", coreAuthToggleGroup.getSelectedToggle().getUserData() == CoreAuthType.COOKIE && (newValue.isEmpty() || getDirectory(newValue) == null))
|
||||||
|
));
|
||||||
|
|
||||||
|
validationSupport.registerValidator(coreUser, Validator.combine(
|
||||||
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Core user required", coreAuthToggleGroup.getSelectedToggle().getUserData() == CoreAuthType.USERPASS && newValue.isEmpty())
|
||||||
|
));
|
||||||
|
|
||||||
|
validationSupport.registerValidator(corePass, Validator.combine(
|
||||||
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Core pass required", coreAuthToggleGroup.getSelectedToggle().getUserData() == CoreAuthType.USERPASS && newValue.isEmpty())
|
||||||
|
));
|
||||||
|
|
||||||
|
validationSupport.registerValidator(coreWallet, Validator.combine(
|
||||||
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Core wallet required", coreMultiWallet.isSelected() && newValue.isEmpty())
|
||||||
|
));
|
||||||
|
|
||||||
|
validationSupport.registerValidator(electrumHost, Validator.combine(
|
||||||
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid Electrum host", getHost(newValue) == null)
|
||||||
|
));
|
||||||
|
|
||||||
|
validationSupport.registerValidator(electrumPort, Validator.combine(
|
||||||
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid Electrum port", !newValue.isEmpty() && !isValidPort(Integer.parseInt(newValue)))
|
||||||
));
|
));
|
||||||
|
|
||||||
validationSupport.registerValidator(proxyHost, Validator.combine(
|
validationSupport.registerValidator(proxyHost, Validator.combine(
|
||||||
|
@ -269,13 +454,44 @@ public class ServerPreferencesController extends PreferencesDetailController {
|
||||||
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid proxy port", !newValue.isEmpty() && !isValidPort(Integer.parseInt(newValue)))
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid proxy port", !newValue.isEmpty() && !isValidPort(Integer.parseInt(newValue)))
|
||||||
));
|
));
|
||||||
|
|
||||||
validationSupport.registerValidator(certificate, Validator.combine(
|
validationSupport.registerValidator(electrumCertificate, Validator.combine(
|
||||||
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid certificate file", newValue != null && !newValue.isEmpty() && getCertificate(newValue) == null)
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid certificate file", newValue != null && !newValue.isEmpty() && getCertificate(newValue) == null)
|
||||||
));
|
));
|
||||||
|
|
||||||
validationSupport.setValidationDecorator(new StyleClassValidationDecoration());
|
validationSupport.setValidationDecorator(new StyleClassValidationDecoration());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private ChangeListener<String> getBitcoinCoreListener(Config config) {
|
||||||
|
return (observable, oldValue, newValue) -> {
|
||||||
|
setCoreServerInConfig(config);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setCoreServerInConfig(Config config) {
|
||||||
|
String hostAsString = getHost(coreHost.getText());
|
||||||
|
Integer portAsInteger = getPort(corePort.getText());
|
||||||
|
if(hostAsString != null && portAsInteger != null && isValidPort(portAsInteger)) {
|
||||||
|
config.setCoreServer(Protocol.HTTP.toUrlString(hostAsString, portAsInteger));
|
||||||
|
} else if(hostAsString != null) {
|
||||||
|
config.setCoreServer(Protocol.HTTP.toUrlString(hostAsString));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private ChangeListener<String> getBitcoinAuthListener(Config config) {
|
||||||
|
return (observable, oldValue, newValue) -> {
|
||||||
|
config.setCoreAuth(coreUser.getText() + ":" + corePass.getText());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@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) -> {
|
||||||
|
@ -284,8 +500,8 @@ public class ServerPreferencesController extends PreferencesDetailController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setElectrumServerInConfig(Config config) {
|
private void setElectrumServerInConfig(Config config) {
|
||||||
String hostAsString = getHost(host.getText());
|
String hostAsString = getHost(electrumHost.getText());
|
||||||
Integer portAsInteger = getPort(port.getText());
|
Integer portAsInteger = getPort(electrumPort.getText());
|
||||||
if(hostAsString != null && portAsInteger != null && isValidPort(portAsInteger)) {
|
if(hostAsString != null && portAsInteger != null && isValidPort(portAsInteger)) {
|
||||||
config.setElectrumServer(getProtocol().toUrlString(hostAsString, portAsInteger));
|
config.setElectrumServer(getProtocol().toUrlString(hostAsString, portAsInteger));
|
||||||
} else if(hostAsString != null) {
|
} else if(hostAsString != null) {
|
||||||
|
@ -311,7 +527,7 @@ public class ServerPreferencesController extends PreferencesDetailController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Protocol getProtocol() {
|
private Protocol getProtocol() {
|
||||||
return (useSsl.isSelected() ? Protocol.SSL : Protocol.TCP);
|
return (electrumUseSsl.isSelected() ? Protocol.SSL : Protocol.TCP);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getHost(String text) {
|
private String getHost(String text) {
|
||||||
|
@ -330,6 +546,19 @@ public class ServerPreferencesController extends PreferencesDetailController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private File getDirectory(String dirLocation) {
|
||||||
|
try {
|
||||||
|
File dirFile = new File(dirLocation);
|
||||||
|
if(!dirFile.exists() || !dirFile.isDirectory()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dirFile;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private File getCertificate(String crtFileLocation) {
|
private File getCertificate(String crtFileLocation) {
|
||||||
try {
|
try {
|
||||||
File crtFile = new File(crtFileLocation);
|
File crtFile = new File(crtFileLocation);
|
||||||
|
@ -357,4 +586,31 @@ public class ServerPreferencesController extends PreferencesDetailController {
|
||||||
private static boolean isValidPort(int port) {
|
private static boolean isValidPort(int port) {
|
||||||
return port >= 0 && port <= 65535;
|
return port >= 0 && port <= 65535;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private File getDefaultCoreDataDir() {
|
||||||
|
org.controlsfx.tools.Platform platform = org.controlsfx.tools.Platform.getCurrent();
|
||||||
|
if(platform == org.controlsfx.tools.Platform.OSX) {
|
||||||
|
return new File(System.getProperty("user.home") + "/Library/Application Support/Bitcoin");
|
||||||
|
} else if(platform == org.controlsfx.tools.Platform.WINDOWS) {
|
||||||
|
return new File(System.getenv("APPDATA") + "/Bitcoin");
|
||||||
|
} else {
|
||||||
|
return new File(System.getProperty("user.home") + "/.bitcoin");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setTestResultsFont() {
|
||||||
|
org.controlsfx.tools.Platform platform = org.controlsfx.tools.Platform.getCurrent();
|
||||||
|
if(platform == org.controlsfx.tools.Platform.OSX) {
|
||||||
|
testResults.setFont(Font.font("Monaco", 11));
|
||||||
|
} else if(platform == org.controlsfx.tools.Platform.WINDOWS) {
|
||||||
|
testResults.setFont(Font.font("Lucida Console", 11));
|
||||||
|
} else {
|
||||||
|
testResults.setFont(Font.font("monospace", 11));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void bwtStatus(BwtStatusEvent event) {
|
||||||
|
testResults.appendText("\n" + event.getStatus());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,13 +5,19 @@ import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.event.SettingsChangedEvent;
|
import com.sparrowwallet.sparrow.event.SettingsChangedEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.fxml.Initializable;
|
import javafx.fxml.Initializable;
|
||||||
|
import javafx.scene.control.DatePicker;
|
||||||
import javafx.scene.control.Spinner;
|
import javafx.scene.control.Spinner;
|
||||||
import javafx.scene.control.SpinnerValueFactory;
|
import javafx.scene.control.SpinnerValueFactory;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
public class AdvancedController implements Initializable {
|
public class AdvancedController implements Initializable {
|
||||||
|
@FXML
|
||||||
|
private DatePicker birthDate;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Spinner<Integer> gapLimit;
|
private Spinner<Integer> gapLimit;
|
||||||
|
|
||||||
|
@ -21,6 +27,15 @@ public class AdvancedController implements Initializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initializeView(Wallet wallet) {
|
public void initializeView(Wallet wallet) {
|
||||||
|
if(wallet.getBirthDate() != null) {
|
||||||
|
birthDate.setValue(wallet.getBirthDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
|
||||||
|
}
|
||||||
|
birthDate.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
if(newValue != null) {
|
||||||
|
wallet.setBirthDate(Date.from(newValue.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
gapLimit.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(Wallet.DEFAULT_LOOKAHEAD, 10000, wallet.getGapLimit()));
|
gapLimit.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(Wallet.DEFAULT_LOOKAHEAD, 10000, wallet.getGapLimit()));
|
||||||
gapLimit.valueProperty().addListener((observable, oldValue, newValue) -> {
|
gapLimit.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
wallet.setGapLimit(newValue);
|
wallet.setGapLimit(newValue);
|
||||||
|
|
|
@ -128,6 +128,16 @@ public class TransactionsController extends WalletFormController implements Init
|
||||||
transactionsTable.updateHistoryStatus(event);
|
transactionsTable.updateHistoryStatus(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void bwtSyncStatus(BwtSyncStatusEvent event) {
|
||||||
|
walletHistoryStatus(new WalletHistoryStatusEvent(walletForm.getWallet(), false, event.getStatus()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void bwtScanStatus(BwtScanStatusEvent event) {
|
||||||
|
walletHistoryStatus(new WalletHistoryStatusEvent(walletForm.getWallet(), false, event.getStatus()));
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void walletUtxoStatusChanged(WalletUtxoStatusChangedEvent event) {
|
public void walletUtxoStatusChanged(WalletUtxoStatusChangedEvent event) {
|
||||||
if(event.getWallet().equals(getWalletForm().getWallet())) {
|
if(event.getWallet().equals(getWalletForm().getWallet())) {
|
||||||
|
|
|
@ -133,6 +133,17 @@ public class UtxosController extends WalletFormController implements Initializab
|
||||||
utxosTable.updateHistoryStatus(event);
|
utxosTable.updateHistoryStatus(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void bwtSyncStatus(BwtSyncStatusEvent event) {
|
||||||
|
walletHistoryStatus(new WalletHistoryStatusEvent(walletForm.getWallet(), false, event.getStatus()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void bwtScanStatus(BwtScanStatusEvent event) {
|
||||||
|
walletHistoryStatus(new WalletHistoryStatusEvent(walletForm.getWallet(), false, event.getStatus()));
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void walletUtxoStatusChanged(WalletUtxoStatusChangedEvent event) {
|
public void walletUtxoStatusChanged(WalletUtxoStatusChangedEvent event) {
|
||||||
if(event.getWallet().equals(getWalletForm().getWallet())) {
|
if(event.getWallet().equals(getWalletForm().getWallet())) {
|
||||||
|
|
|
@ -25,4 +25,5 @@ open module com.sparrowwallet.sparrow {
|
||||||
requires centerdevice.nsmenufx;
|
requires centerdevice.nsmenufx;
|
||||||
requires jcommander;
|
requires jcommander;
|
||||||
requires slf4j.api;
|
requires slf4j.api;
|
||||||
|
requires bwt.jni;
|
||||||
}
|
}
|
|
@ -40,6 +40,10 @@
|
||||||
-fx-padding: -20 0 0 0;
|
-fx-padding: -20 0 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form .field .toggle-switch {
|
||||||
|
-fx-padding: 5 0 2 0;
|
||||||
|
}
|
||||||
|
|
||||||
.tab-error > .tab-container {
|
.tab-error > .tab-container {
|
||||||
-fx-effect: dropshadow(three-pass-box, rgba(202, 18, 67, .6), 7, 0, 0, 0);
|
-fx-effect: dropshadow(three-pass-box, rgba(202, 18, 67, .6), 7, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,10 @@
|
||||||
<?import tornadofx.control.Field?>
|
<?import tornadofx.control.Field?>
|
||||||
<?import com.sparrowwallet.sparrow.control.UnlabeledToggleSwitch?>
|
<?import com.sparrowwallet.sparrow.control.UnlabeledToggleSwitch?>
|
||||||
<?import org.controlsfx.glyphfont.Glyph?>
|
<?import org.controlsfx.glyphfont.Glyph?>
|
||||||
|
<?import org.controlsfx.control.SegmentedButton?>
|
||||||
|
<?import com.sparrowwallet.sparrow.net.ServerType?>
|
||||||
|
<?import com.sparrowwallet.sparrow.net.CoreAuthType?>
|
||||||
|
<?import com.sparrowwallet.sparrow.control.HelpLabel?>
|
||||||
<GridPane hgap="10.0" vgap="10.0" stylesheets="@preferences.css, @../general.css" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.preferences.ServerPreferencesController">
|
<GridPane hgap="10.0" vgap="10.0" stylesheets="@preferences.css, @../general.css" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.preferences.ServerPreferencesController">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets left="25.0" right="25.0" top="25.0" />
|
<Insets left="25.0" right="25.0" top="25.0" />
|
||||||
|
@ -24,25 +28,92 @@
|
||||||
</rowConstraints>
|
</rowConstraints>
|
||||||
|
|
||||||
<Form GridPane.columnIndex="0" GridPane.rowIndex="0">
|
<Form GridPane.columnIndex="0" GridPane.rowIndex="0">
|
||||||
<Fieldset inputGrow="SOMETIMES" text="Electrum Server">
|
<Fieldset inputGrow="SOMETIMES" text="Server">
|
||||||
|
<Field text="Type:">
|
||||||
|
<SegmentedButton>
|
||||||
|
<toggleGroup>
|
||||||
|
<ToggleGroup fx:id="serverTypeToggleGroup" />
|
||||||
|
</toggleGroup>
|
||||||
|
<buttons>
|
||||||
|
<ToggleButton text="Bitcoin Core" toggleGroup="$serverTypeToggleGroup">
|
||||||
|
<userData>
|
||||||
|
<ServerType fx:constant="BITCOIN_CORE"/>
|
||||||
|
</userData>
|
||||||
|
</ToggleButton>
|
||||||
|
<ToggleButton text="Electrum Server" toggleGroup="$serverTypeToggleGroup">
|
||||||
|
<userData>
|
||||||
|
<ServerType fx:constant="ELECTRUM_SERVER"/>
|
||||||
|
</userData>
|
||||||
|
</ToggleButton>
|
||||||
|
</buttons>
|
||||||
|
</SegmentedButton>
|
||||||
|
</Field>
|
||||||
|
</Fieldset>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
<Form fx:id="coreForm" GridPane.columnIndex="0" GridPane.rowIndex="1">
|
||||||
|
<Fieldset inputGrow="SOMETIMES" text="Bitcoin Core RPC">
|
||||||
<Field text="URL:">
|
<Field text="URL:">
|
||||||
<TextField fx:id="host" promptText="e.g. 127.0.0.1"/>
|
<TextField fx:id="coreHost" promptText="e.g. 127.0.0.1"/>
|
||||||
<TextField fx:id="port" promptText="e.g. 50002" prefWidth="80" />
|
<TextField fx:id="corePort" promptText="e.g. 8332" prefWidth="80" />
|
||||||
</Field>
|
</Field>
|
||||||
<Field text="Use SSL:">
|
<Field text="Authentication:">
|
||||||
<UnlabeledToggleSwitch fx:id="useSsl"/>
|
<SegmentedButton>
|
||||||
|
<toggleGroup>
|
||||||
|
<ToggleGroup fx:id="coreAuthToggleGroup" />
|
||||||
|
</toggleGroup>
|
||||||
|
<buttons>
|
||||||
|
<ToggleButton text="Default" toggleGroup="$coreAuthToggleGroup">
|
||||||
|
<userData>
|
||||||
|
<CoreAuthType fx:constant="COOKIE"/>
|
||||||
|
</userData>
|
||||||
|
</ToggleButton>
|
||||||
|
<ToggleButton text="User / Pass" toggleGroup="$coreAuthToggleGroup">
|
||||||
|
<userData>
|
||||||
|
<CoreAuthType fx:constant="USERPASS"/>
|
||||||
|
</userData>
|
||||||
|
</ToggleButton>
|
||||||
|
</buttons>
|
||||||
|
</SegmentedButton>
|
||||||
</Field>
|
</Field>
|
||||||
<Field text="Certificate:" styleClass="label-button">
|
<Field fx:id="coreDataDirField" text="Data Folder:" styleClass="label-button">
|
||||||
<TextField fx:id="certificate" promptText="Optional server certificate (.crt)"/>
|
<TextField fx:id="coreDataDir"/>
|
||||||
<Button fx:id="certificateSelect" maxWidth="25" minWidth="-Infinity" prefWidth="30" text="Ed">
|
<Button fx:id="coreDataDirSelect" maxWidth="35" minWidth="-Infinity" prefWidth="35">
|
||||||
<graphic>
|
<graphic>
|
||||||
<Glyph fontFamily="FontAwesome" icon="EDIT" prefWidth="15" />
|
<Glyph fontFamily="FontAwesome" icon="EDIT" fontSize="13" />
|
||||||
</graphic>
|
</graphic>
|
||||||
</Button>
|
</Button>
|
||||||
</Field>
|
</Field>
|
||||||
|
<Field fx:id="coreUserPassField" text="User / Pass:" styleClass="label-button">
|
||||||
|
<TextField fx:id="coreUser"/>
|
||||||
|
<PasswordField fx:id="corePass"/>
|
||||||
|
</Field>
|
||||||
|
<Field text="Multi-Wallet:">
|
||||||
|
<UnlabeledToggleSwitch fx:id="coreMultiWallet"/> <HelpLabel helpText="Enable this if using multiple Bitcoin Core wallets" />
|
||||||
|
</Field>
|
||||||
|
<Field text="Wallet Name:" styleClass="label-button">
|
||||||
|
<TextField fx:id="coreWallet"/>
|
||||||
|
</Field>
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
|
</Form>
|
||||||
|
|
||||||
<Fieldset inputGrow="SOMETIMES" text="Proxy">
|
<Form fx:id="electrumForm" GridPane.columnIndex="0" GridPane.rowIndex="1">
|
||||||
|
<Fieldset inputGrow="SOMETIMES" text="Electrum Server">
|
||||||
|
<Field text="URL:">
|
||||||
|
<TextField fx:id="electrumHost" promptText="e.g. 127.0.0.1"/>
|
||||||
|
<TextField fx:id="electrumPort" promptText="e.g. 50002" prefWidth="80" />
|
||||||
|
</Field>
|
||||||
|
<Field text="Use SSL:">
|
||||||
|
<UnlabeledToggleSwitch fx:id="electrumUseSsl"/>
|
||||||
|
</Field>
|
||||||
|
<Field text="Certificate:" styleClass="label-button">
|
||||||
|
<TextField fx:id="electrumCertificate" promptText="Optional server certificate (.crt)"/>
|
||||||
|
<Button fx:id="electrumCertificateSelect" maxWidth="35" minWidth="-Infinity" prefWidth="35">
|
||||||
|
<graphic>
|
||||||
|
<Glyph fontFamily="FontAwesome" icon="EDIT" fontSize="13" />
|
||||||
|
</graphic>
|
||||||
|
</Button>
|
||||||
|
</Field>
|
||||||
<Field text="Use Proxy:">
|
<Field text="Use Proxy:">
|
||||||
<UnlabeledToggleSwitch fx:id="useProxy"/>
|
<UnlabeledToggleSwitch fx:id="useProxy"/>
|
||||||
</Field>
|
</Field>
|
||||||
|
@ -53,7 +124,7 @@
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
<StackPane GridPane.columnIndex="0" GridPane.rowIndex="1">
|
<StackPane GridPane.columnIndex="0" GridPane.rowIndex="2">
|
||||||
<Button fx:id="testConnection" graphicTextGap="5" text="Test Connection">
|
<Button fx:id="testConnection" graphicTextGap="5" text="Test Connection">
|
||||||
<graphic>
|
<graphic>
|
||||||
<Glyph fontFamily="FontAwesome" icon="QUESTION_CIRCLE" prefWidth="13" />
|
<Glyph fontFamily="FontAwesome" icon="QUESTION_CIRCLE" prefWidth="13" />
|
||||||
|
@ -66,7 +137,7 @@
|
||||||
</Button>
|
</Button>
|
||||||
</StackPane>
|
</StackPane>
|
||||||
|
|
||||||
<StackPane GridPane.columnIndex="0" GridPane.rowIndex="2">
|
<StackPane GridPane.columnIndex="0" GridPane.rowIndex="3">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets top="10.0" bottom="20.0"/>
|
<Insets top="10.0" bottom="20.0"/>
|
||||||
</padding>
|
</padding>
|
||||||
|
|
|
@ -25,7 +25,11 @@
|
||||||
</rowConstraints>
|
</rowConstraints>
|
||||||
|
|
||||||
<Form GridPane.columnIndex="0" GridPane.rowIndex="0">
|
<Form GridPane.columnIndex="0" GridPane.rowIndex="0">
|
||||||
<Fieldset inputGrow="SOMETIMES" text="Advanced Settings" styleClass="wideLabelFieldSet">
|
<Fieldset inputGrow="SOMETIMES" text="Advanced Settings">
|
||||||
|
<Field text="Birth date:">
|
||||||
|
<DatePicker editable="false" fx:id="birthDate" prefWidth="140" />
|
||||||
|
<HelpLabel helpText="The date of the earliest transaction (used to avoid scanning the entire blockchain)."/>
|
||||||
|
</Field>
|
||||||
<Field text="Gap limit:">
|
<Field text="Gap limit:">
|
||||||
<Spinner fx:id="gapLimit" editable="true" prefWidth="90" />
|
<Spinner fx:id="gapLimit" editable="true" prefWidth="90" />
|
||||||
<HelpLabel helpText="Change how far ahead to look for additional transactions beyond the highest derivation with previous transaction outputs."/>
|
<HelpLabel helpText="Change how far ahead to look for additional transactions beyond the highest derivation with previous transaction outputs."/>
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<root level="info">
|
<root level="debug">
|
||||||
<appender-ref ref="FILE" />
|
<appender-ref ref="FILE" />
|
||||||
<appender-ref ref="STDOUT" />
|
<appender-ref ref="STDOUT" />
|
||||||
</root>
|
</root>
|
||||||
|
|
BIN
src/main/resources/native/linux/x64/libbwt.so
Executable file
BIN
src/main/resources/native/linux/x64/libbwt.so
Executable file
Binary file not shown.
BIN
src/main/resources/native/osx/x64/libbwt.dylib
Normal file
BIN
src/main/resources/native/osx/x64/libbwt.dylib
Normal file
Binary file not shown.
BIN
src/main/resources/native/windows/x64/bwt.dll
Executable file
BIN
src/main/resources/native/windows/x64/bwt.dll
Executable file
Binary file not shown.
Loading…
Reference in a new issue