upgrade to bwt with earlier termination, various bitcoin core related ui changes

This commit is contained in:
Craig Raw 2021-01-11 17:31:28 +02:00
parent 628b15a3b5
commit 546ccd66b3
13 changed files with 99 additions and 66 deletions

View file

@ -69,7 +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.5') implementation('dev.bwt:bwt-jni:0.1.6')
testImplementation('junit:junit:4.12') testImplementation('junit:junit:4.12')
} }

View file

@ -23,6 +23,7 @@ import com.sparrowwallet.sparrow.control.*;
import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.io.*; import com.sparrowwallet.sparrow.io.*;
import com.sparrowwallet.sparrow.net.ElectrumServer; import com.sparrowwallet.sparrow.net.ElectrumServer;
import com.sparrowwallet.sparrow.net.ServerType;
import com.sparrowwallet.sparrow.preferences.PreferencesDialog; import com.sparrowwallet.sparrow.preferences.PreferencesDialog;
import com.sparrowwallet.sparrow.transaction.TransactionController; import com.sparrowwallet.sparrow.transaction.TransactionController;
import com.sparrowwallet.sparrow.transaction.TransactionData; import com.sparrowwallet.sparrow.transaction.TransactionData;
@ -223,6 +224,7 @@ public class AppController implements Initializable {
showTxHex.setSelected(Config.get().isShowTransactionHex()); showTxHex.setSelected(Config.get().isShowTransactionHex());
exportWallet.setDisable(true); exportWallet.setDisable(true);
setServerType(Config.get().getServerType());
serverToggle.setSelected(isConnected()); serverToggle.setSelected(isConnected());
onlineProperty().bindBidirectional(serverToggle.selectedProperty()); onlineProperty().bindBidirectional(serverToggle.selectedProperty());
onlineProperty().addListener((observable, oldValue, newValue) -> { onlineProperty().addListener((observable, oldValue, newValue) -> {
@ -1042,6 +1044,14 @@ public class AppController implements Initializable {
return contextMenu; return contextMenu;
} }
public void setServerType(ServerType serverType) {
if(serverType == ServerType.BITCOIN_CORE && !serverToggle.getStyleClass().contains("core-server")) {
serverToggle.getStyleClass().add("core-server");
} else {
serverToggle.getStyleClass().remove("core-server");
}
}
public void setTheme(ActionEvent event) { public void setTheme(ActionEvent event) {
Theme selectedTheme = (Theme)theme.getSelectedToggle().getUserData(); Theme selectedTheme = (Theme)theme.getSelectedToggle().getUserData();
if(Config.get().getTheme() != selectedTheme) { if(Config.get().getTheme() != selectedTheme) {
@ -1063,6 +1073,11 @@ public class AppController implements Initializable {
} }
} }
@Subscribe
public void serverTypeChanged(ServerTypeChangedEvent event) {
setServerType(event.getServerType());
}
@Subscribe @Subscribe
public void tabSelected(TabSelectedEvent event) { public void tabSelected(TabSelectedEvent event) {
if(tabs.getTabs().contains(event.getTab())) { if(tabs.getTabs().contains(event.getTab())) {

View file

@ -2,6 +2,7 @@ package com.sparrowwallet.sparrow.control;
import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.Mode; import com.sparrowwallet.sparrow.Mode;
import com.sparrowwallet.sparrow.net.ServerType;
import javafx.application.HostServices; import javafx.application.HostServices;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.scene.control.*; import javafx.scene.control.*;
@ -13,13 +14,10 @@ import org.controlsfx.control.StatusBar;
import org.controlsfx.control.ToggleSwitch; import org.controlsfx.control.ToggleSwitch;
public class WelcomeDialog extends Dialog<Mode> { public class WelcomeDialog extends Dialog<Mode> {
private static final String[] ELECTRUM_SERVERS = new String[]{
"ElectrumX (Recommended)", "https://github.com/spesmilo/electrumx",
"electrs", "https://github.com/romanz/electrs",
"esplora-electrs", "https://github.com/Blockstream/electrs"};
private final HostServices hostServices; private final HostServices hostServices;
private ServerType serverType = ServerType.ELECTRUM_SERVER;
public WelcomeDialog(HostServices services) { public WelcomeDialog(HostServices services) {
this.hostServices = services; this.hostServices = services;
@ -30,7 +28,7 @@ public class WelcomeDialog extends Dialog<Mode> {
dialogPane.getStylesheets().add(AppServices.class.getResource("general.css").toExternalForm()); dialogPane.getStylesheets().add(AppServices.class.getResource("general.css").toExternalForm());
AppServices.setStageIcon(dialogPane.getScene().getWindow()); AppServices.setStageIcon(dialogPane.getScene().getWindow());
dialogPane.setPrefWidth(600); dialogPane.setPrefWidth(600);
dialogPane.setPrefHeight(480); dialogPane.setPrefHeight(520);
Image image = new Image("image/sparrow-small.png", 50, 50, false, false); Image image = new Image("image/sparrow-small.png", 50, 50, false, false);
if (!image.isError()) { if (!image.isError()) {
@ -46,16 +44,10 @@ public class WelcomeDialog extends Dialog<Mode> {
final VBox content = new VBox(20); final VBox content = new VBox(20);
content.setPadding(new Insets(20, 20, 20, 20)); content.setPadding(new Insets(20, 20, 20, 20));
content.getChildren().add(createParagraph("Sparrow can operate in both an online and offline mode. In the online mode it connects to your Electrum server to display transaction history. In the offline mode it is useful as a transaction editor and as an airgapped multisig coordinator.")); content.getChildren().add(createParagraph("Sparrow can operate in both an online and offline mode. In the online mode it connects to your Bitcoin Core node or Electrum server to display transaction history. In the offline mode it is useful as a transaction editor and as an airgapped multisig coordinator."));
content.getChildren().add(createParagraph("For privacy and security reasons it is not recommended to use a public Electrum server. Install an Electrum server that connects to your full node to index the blockchain and provide full privacy. Examples include:")); content.getChildren().add(createParagraph("Connecting Sparrow to your Bitcoin Core node ensures your privacy, while connecting Sparrow to your own Electrum server ensures wallets load quicker, you have access to a full blockchain explorer, and your public keys are always encrypted on disk. Examples of Electrum servers include ElectrumX and electrs."));
content.getChildren().add(createParagraph("It's also possible to connect Sparrow to a public Electrum server (such as blockstream.info:700) but this is not recommended as you will share your public key information with that server."));
VBox linkBox = new VBox(); content.getChildren().add(createParagraph("You can change your mode at any time using the toggle in the status bar. A blue toggle indicates you are connected to an Electrum server, while a green toggle indicates you are connected to a Bitcoin Code node."));
for(int i = 0; i < ELECTRUM_SERVERS.length; i+=2) {
linkBox.getChildren().add(createBulletedLink(ELECTRUM_SERVERS[i], ELECTRUM_SERVERS[i+1]));
}
content.getChildren().add(linkBox);
content.getChildren().add(createParagraph("You can change your mode at any time using the toggle in the status bar:"));
content.getChildren().add(createStatusBar(onlineButtonType, offlineButtonType)); content.getChildren().add(createStatusBar(onlineButtonType, offlineButtonType));
dialogPane.setContent(content); dialogPane.setContent(content);
@ -70,16 +62,6 @@ public class WelcomeDialog extends Dialog<Mode> {
return label; return label;
} }
private HyperlinkLabel createBulletedLink(String name, String url) {
String[] nameParts = name.split(" ");
HyperlinkLabel label = new HyperlinkLabel(" \u2022 [" + nameParts[0] + "] " + (nameParts.length > 1 ? nameParts[1] : ""));
label.setOnAction(event -> {
hostServices.showDocument(url);
});
return label;
}
private StatusBar createStatusBar(ButtonType onlineButtonType, ButtonType offlineButtonType) { private StatusBar createStatusBar(ButtonType onlineButtonType, ButtonType offlineButtonType) {
StatusBar statusBar = new StatusBar(); StatusBar statusBar = new StatusBar();
statusBar.setText("Online Mode"); statusBar.setText("Online Mode");
@ -97,7 +79,18 @@ public class WelcomeDialog extends Dialog<Mode> {
onlineButton.setDefaultButton(newValue); onlineButton.setDefaultButton(newValue);
Button offlineButton = (Button) getDialogPane().lookupButton(offlineButtonType); Button offlineButton = (Button) getDialogPane().lookupButton(offlineButtonType);
offlineButton.setDefaultButton(!newValue); offlineButton.setDefaultButton(!newValue);
statusBar.setText(newValue ? "Online Mode" : "Offline Mode");
if(!newValue) {
serverType = (serverType == ServerType.BITCOIN_CORE ? ServerType.ELECTRUM_SERVER : ServerType.BITCOIN_CORE);
if(serverType == ServerType.BITCOIN_CORE && !toggleSwitch.getStyleClass().contains("core-server")) {
toggleSwitch.getStyleClass().add("core-server");
} else {
toggleSwitch.getStyleClass().remove("core-server");
}
}
statusBar.setText(newValue ? "Online Mode: " + serverType.getName() : "Offline Mode");
}); });
toggleSwitch.setSelected(true); toggleSwitch.setSelected(true);

View file

@ -1,14 +1,7 @@
package com.sparrowwallet.sparrow.event; package com.sparrowwallet.sparrow.event;
public class BwtReadyStatusEvent extends BwtStatusEvent { public class BwtReadyStatusEvent extends BwtStatusEvent {
private final long shutdownPtr; public BwtReadyStatusEvent(String status) {
public BwtReadyStatusEvent(String status, long shutdownPtr) {
super(status); super(status);
this.shutdownPtr = shutdownPtr;
}
public long getShutdownPtr() {
return shutdownPtr;
} }
} }

View file

@ -1,10 +1,12 @@
package com.sparrowwallet.sparrow.event; package com.sparrowwallet.sparrow.event;
import java.util.Date;
public class BwtSyncStatusEvent extends BwtStatusEvent { public class BwtSyncStatusEvent extends BwtStatusEvent {
private final int progress; private final int progress;
private final int tip; private final Date tip;
public BwtSyncStatusEvent(String status, int progress, int tip) { public BwtSyncStatusEvent(String status, int progress, Date tip) {
super(status); super(status);
this.progress = progress; this.progress = progress;
this.tip = tip; this.tip = tip;
@ -18,7 +20,7 @@ public class BwtSyncStatusEvent extends BwtStatusEvent {
return progress == 100; return progress == 100;
} }
public int getTip() { public Date getTip() {
return tip; return tip;
} }
} }

View file

@ -0,0 +1,15 @@
package com.sparrowwallet.sparrow.event;
import com.sparrowwallet.sparrow.net.ServerType;
public class ServerTypeChangedEvent {
private final ServerType serverType;
public ServerTypeChangedEvent(ServerType serverType) {
this.serverType = serverType;
}
public ServerType getServerType() {
return serverType;
}
}

View file

@ -204,7 +204,8 @@ public class BatchedElectrumServerRpc implements ElectrumServerRpc {
} }
try { try {
return new RetryLogic<Map<String, VerboseTransaction>>(MAX_RETRIES, RETRY_DELAY, IllegalStateException.class).getResult(batchRequest::execute); //The server may return an error if the transaction has not yet been broadcasted - this is a valid state so only try once
return new RetryLogic<Map<String, VerboseTransaction>>(1, RETRY_DELAY, IllegalStateException.class).getResult(batchRequest::execute);
} catch(JsonRpcBatchException e) { } catch(JsonRpcBatchException e) {
log.warn("Some errors retrieving transactions: " + e.getErrors()); log.warn("Some errors retrieving transactions: " + e.getErrors());
return (Map<String, VerboseTransaction>)e.getSuccesses(); return (Map<String, VerboseTransaction>)e.getSuccesses();

View file

@ -23,6 +23,7 @@ import java.util.*;
public class Bwt { public class Bwt {
private static final Logger log = LoggerFactory.getLogger(Bwt.class); private static final Logger log = LoggerFactory.getLogger(Bwt.class);
private static final int IMPORT_BATCH_SIZE = 350;
private Long shutdownPtr; private Long shutdownPtr;
private boolean terminating; private boolean terminating;
@ -85,7 +86,8 @@ public class Bwt {
bwtConfig.descriptors = outputDescriptors; bwtConfig.descriptors = outputDescriptors;
bwtConfig.rescanSince = (rescanSince == null || rescanSince < 0 ? "now" : rescanSince); bwtConfig.rescanSince = (rescanSince == null || rescanSince < 0 ? "now" : rescanSince);
bwtConfig.forceRescan = forceRescan; bwtConfig.forceRescan = forceRescan;
bwtConfig.gapLimit = gapLimit; //bwtConfig.initialImportSize = IMPORT_BATCH_SIZE;
bwtConfig.gapLimit = IMPORT_BATCH_SIZE;
} else { } else {
bwtConfig.requireAddresses = false; bwtConfig.requireAddresses = false;
} }
@ -220,9 +222,14 @@ public class Bwt {
protected Void call() { protected Void call() {
CallbackNotifier notifier = new CallbackNotifier() { CallbackNotifier notifier = new CallbackNotifier() {
@Override @Override
public void onBooting() { public void onBooting(long shutdownPtr) {
log.debug("Booting bwt"); log.debug("Booting bwt");
if(!terminating) {
Bwt.this.shutdownPtr = shutdownPtr;
if(terminating) {
Bwt.this.shutdown();
terminating = false;
} else {
Platform.runLater(() -> EventManager.get().post(new BwtBootStatusEvent("Connecting to Bitcoin Core node at " + Config.get().getCoreServer() + "..."))); Platform.runLater(() -> EventManager.get().post(new BwtBootStatusEvent("Connecting to Bitcoin Core node at " + Config.get().getCoreServer() + "...")));
} }
} }
@ -230,9 +237,10 @@ public class Bwt {
@Override @Override
public void onSyncProgress(float progress, int tip) { public void onSyncProgress(float progress, int tip) {
int percent = (int) (progress * 100.0); int percent = (int) (progress * 100.0);
Date tipDate = new Date((long)tip * 1000);
log.debug("Syncing " + percent + "%"); log.debug("Syncing " + percent + "%");
if(!terminating) { if(!terminating) {
Platform.runLater(() -> EventManager.get().post(new BwtSyncStatusEvent("Syncing" + (percent < 100 ? " (" + percent + "%)" : ""), percent, tip))); Platform.runLater(() -> EventManager.get().post(new BwtSyncStatusEvent("Syncing" + (percent < 100 ? " (" + percent + "%)" : ""), percent, tipDate)));
} }
} }
@ -260,14 +268,10 @@ public class Bwt {
} }
@Override @Override
public void onReady(long shutdownPtr) { public void onReady() {
log.debug("Bwt ready"); log.debug("Bwt ready");
Bwt.this.shutdownPtr = shutdownPtr; if(!terminating) {
if(terminating) { Platform.runLater(() -> EventManager.get().post(new BwtReadyStatusEvent("Server ready")));
Bwt.this.shutdown();
terminating = false;
} else {
Platform.runLater(() -> EventManager.get().post(new BwtReadyStatusEvent("Server ready", shutdownPtr)));
} }
} }
}; };

View file

@ -54,7 +54,7 @@ public class ElectrumServer {
if(Config.get().getServerType() == ServerType.BITCOIN_CORE) { if(Config.get().getServerType() == ServerType.BITCOIN_CORE) {
if(bwtElectrumServer == null) { if(bwtElectrumServer == null) {
throw new ServerException("BWT server not started"); throw new ServerException("Could not connect to Bitcoin Core RPC");
} }
electrumServer = bwtElectrumServer; electrumServer = bwtElectrumServer;
} else if(Config.get().getServerType() == ServerType.ELECTRUM_SERVER) { } else if(Config.get().getServerType() == ServerType.ELECTRUM_SERVER) {
@ -928,14 +928,16 @@ public class ElectrumServer {
} }
private void shutdownBwt() { private void shutdownBwt() {
Bwt.DisconnectionService disconnectionService = bwt.getDisconnectionService(); if(Config.get().getServerType() == ServerType.BITCOIN_CORE) {
disconnectionService.setOnSucceeded(workerStateEvent -> { Bwt.DisconnectionService disconnectionService = bwt.getDisconnectionService();
ElectrumServer.bwtElectrumServer = null; disconnectionService.setOnSucceeded(workerStateEvent -> {
}); ElectrumServer.bwtElectrumServer = null;
disconnectionService.setOnFailed(workerStateEvent -> { });
log.error("Failed to stop BWT", workerStateEvent.getSource().getException()); disconnectionService.setOnFailed(workerStateEvent -> {
}); log.error("Failed to stop BWT", workerStateEvent.getSource().getException());
Platform.runLater(disconnectionService::start); });
Platform.runLater(disconnectionService::start);
}
} }
@Override @Override

View file

@ -181,7 +181,8 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc {
Map<String, VerboseTransaction> result = new LinkedHashMap<>(); Map<String, VerboseTransaction> result = new LinkedHashMap<>();
for(String txid : txids) { for(String txid : txids) {
try { try {
VerboseTransaction verboseTransaction = new RetryLogic<VerboseTransaction>(MAX_RETRIES, RETRY_DELAY, IllegalStateException.class).getResult(() -> //The server may return an error if the transaction has not yet been broadcasted - this is a valid state so only try once
VerboseTransaction verboseTransaction = new RetryLogic<VerboseTransaction>(1, RETRY_DELAY, IllegalStateException.class).getResult(() ->
client.createRequest().returnAs(VerboseTransaction.class).method("blockchain.transaction.get").id(idCounter.incrementAndGet()).params(txid, true).execute()); client.createRequest().returnAs(VerboseTransaction.class).method("blockchain.transaction.get").id(idCounter.incrementAndGet()).params(txid, true).execute());
result.put(txid, verboseTransaction); result.put(txid, verboseTransaction);
} catch(Exception e) { } catch(Exception e) {

View file

@ -6,10 +6,7 @@ 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.*;
import com.sparrowwallet.sparrow.event.BwtSyncStatusEvent;
import com.sparrowwallet.sparrow.event.ConnectionEvent;
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.*; import com.sparrowwallet.sparrow.net.*;
@ -38,6 +35,8 @@ import javax.net.ssl.SSLHandshakeException;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.List; import java.util.List;
public class ServerPreferencesController extends PreferencesDetailController { public class ServerPreferencesController extends PreferencesDetailController {
@ -141,6 +140,7 @@ public class ServerPreferencesController extends PreferencesDetailController {
config.setServerType(serverType); config.setServerType(serverType);
testConnection.setGraphic(getGlyph(FontAwesome5.Glyph.QUESTION_CIRCLE, "")); testConnection.setGraphic(getGlyph(FontAwesome5.Glyph.QUESTION_CIRCLE, ""));
testResults.clear(); testResults.clear();
EventManager.get().post(new ServerTypeChangedEvent(serverType));
} else if(oldValue != null) { } else if(oldValue != null) {
oldValue.setSelected(true); oldValue.setSelected(true);
} }
@ -626,7 +626,9 @@ public class ServerPreferencesController extends PreferencesDetailController {
@Subscribe @Subscribe
public void bwtSyncStatus(BwtSyncStatusEvent event) { public void bwtSyncStatus(BwtSyncStatusEvent event) {
if(connectionService != null && connectionService.isRunning() && event.getProgress() < 100) { if(connectionService != null && connectionService.isRunning() && event.getProgress() < 100) {
testResults.appendText("\nThe connection to the Bitcoin Core node was successful, but it is still syncing to the the blockchain tip at " + event.getTip() + " blocks (" + event.getProgress() + "% completed)"); DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm");
testResults.appendText("\nThe connection to the Bitcoin Core node was successful, but it is still syncing and cannot be used yet.");
testResults.appendText("\nCurrently " + event.getProgress() + "% completed to date " + dateFormat.format(event.getTip()));
testConnection.setGraphic(getGlyph(FontAwesome5.Glyph.QUESTION_CIRCLE, null)); testConnection.setGraphic(getGlyph(FontAwesome5.Glyph.QUESTION_CIRCLE, null));
connectionService.cancel(); connectionService.cancel();
} }

View file

@ -159,3 +159,8 @@
.root .header-panel { .root .header-panel {
-fx-background-color: -fx-box-border, derive(-fx-background, 10%); -fx-background-color: -fx-box-border, derive(-fx-background, 10%);
} }
.core-server.toggle-switch:selected .thumb-area {
-fx-background-color: linear-gradient(to bottom, derive(-fx-text-box-border, -20%), derive(-fx-text-box-border, -30%)), linear-gradient(to bottom, derive(#50a14f, 30%), #50a14f);
-fx-background-insets: 0, 1;
}