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-media'
}
implementation('dev.bwt:bwt-jni:0.1.5')
implementation('dev.bwt:bwt-jni:0.1.6')
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.io.*;
import com.sparrowwallet.sparrow.net.ElectrumServer;
import com.sparrowwallet.sparrow.net.ServerType;
import com.sparrowwallet.sparrow.preferences.PreferencesDialog;
import com.sparrowwallet.sparrow.transaction.TransactionController;
import com.sparrowwallet.sparrow.transaction.TransactionData;
@ -223,6 +224,7 @@ public class AppController implements Initializable {
showTxHex.setSelected(Config.get().isShowTransactionHex());
exportWallet.setDisable(true);
setServerType(Config.get().getServerType());
serverToggle.setSelected(isConnected());
onlineProperty().bindBidirectional(serverToggle.selectedProperty());
onlineProperty().addListener((observable, oldValue, newValue) -> {
@ -1042,6 +1044,14 @@ public class AppController implements Initializable {
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) {
Theme selectedTheme = (Theme)theme.getSelectedToggle().getUserData();
if(Config.get().getTheme() != selectedTheme) {
@ -1063,6 +1073,11 @@ public class AppController implements Initializable {
}
}
@Subscribe
public void serverTypeChanged(ServerTypeChangedEvent event) {
setServerType(event.getServerType());
}
@Subscribe
public void tabSelected(TabSelectedEvent event) {
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.Mode;
import com.sparrowwallet.sparrow.net.ServerType;
import javafx.application.HostServices;
import javafx.geometry.Insets;
import javafx.scene.control.*;
@ -13,13 +14,10 @@ import org.controlsfx.control.StatusBar;
import org.controlsfx.control.ToggleSwitch;
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 ServerType serverType = ServerType.ELECTRUM_SERVER;
public WelcomeDialog(HostServices services) {
this.hostServices = services;
@ -30,7 +28,7 @@ public class WelcomeDialog extends Dialog<Mode> {
dialogPane.getStylesheets().add(AppServices.class.getResource("general.css").toExternalForm());
AppServices.setStageIcon(dialogPane.getScene().getWindow());
dialogPane.setPrefWidth(600);
dialogPane.setPrefHeight(480);
dialogPane.setPrefHeight(520);
Image image = new Image("image/sparrow-small.png", 50, 50, false, false);
if (!image.isError()) {
@ -46,16 +44,10 @@ public class WelcomeDialog extends Dialog<Mode> {
final VBox content = new VBox(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("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:"));
VBox linkBox = new VBox();
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(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("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."));
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."));
content.getChildren().add(createStatusBar(onlineButtonType, offlineButtonType));
dialogPane.setContent(content);
@ -70,16 +62,6 @@ public class WelcomeDialog extends Dialog<Mode> {
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) {
StatusBar statusBar = new StatusBar();
statusBar.setText("Online Mode");
@ -97,7 +79,18 @@ public class WelcomeDialog extends Dialog<Mode> {
onlineButton.setDefaultButton(newValue);
Button offlineButton = (Button) getDialogPane().lookupButton(offlineButtonType);
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);

View file

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

View file

@ -1,10 +1,12 @@
package com.sparrowwallet.sparrow.event;
import java.util.Date;
public class BwtSyncStatusEvent extends BwtStatusEvent {
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);
this.progress = progress;
this.tip = tip;
@ -18,7 +20,7 @@ public class BwtSyncStatusEvent extends BwtStatusEvent {
return progress == 100;
}
public int getTip() {
public Date getTip() {
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 {
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) {
log.warn("Some errors retrieving transactions: " + e.getErrors());
return (Map<String, VerboseTransaction>)e.getSuccesses();

View file

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

View file

@ -54,7 +54,7 @@ public class ElectrumServer {
if(Config.get().getServerType() == ServerType.BITCOIN_CORE) {
if(bwtElectrumServer == null) {
throw new ServerException("BWT server not started");
throw new ServerException("Could not connect to Bitcoin Core RPC");
}
electrumServer = bwtElectrumServer;
} else if(Config.get().getServerType() == ServerType.ELECTRUM_SERVER) {
@ -928,14 +928,16 @@ public class ElectrumServer {
}
private void shutdownBwt() {
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);
if(Config.get().getServerType() == ServerType.BITCOIN_CORE) {
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

View file

@ -181,7 +181,8 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc {
Map<String, VerboseTransaction> result = new LinkedHashMap<>();
for(String txid : txids) {
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());
result.put(txid, verboseTransaction);
} catch(Exception e) {

View file

@ -6,10 +6,7 @@ import com.sparrowwallet.drongo.Network;
import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.control.TextFieldValidator;
import com.sparrowwallet.sparrow.control.UnlabeledToggleSwitch;
import com.sparrowwallet.sparrow.event.BwtStatusEvent;
import com.sparrowwallet.sparrow.event.BwtSyncStatusEvent;
import com.sparrowwallet.sparrow.event.ConnectionEvent;
import com.sparrowwallet.sparrow.event.RequestDisconnectEvent;
import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
import com.sparrowwallet.sparrow.io.Config;
import com.sparrowwallet.sparrow.net.*;
@ -38,6 +35,8 @@ import javax.net.ssl.SSLHandshakeException;
import java.io.File;
import java.io.FileInputStream;
import java.security.cert.CertificateFactory;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.List;
public class ServerPreferencesController extends PreferencesDetailController {
@ -141,6 +140,7 @@ public class ServerPreferencesController extends PreferencesDetailController {
config.setServerType(serverType);
testConnection.setGraphic(getGlyph(FontAwesome5.Glyph.QUESTION_CIRCLE, ""));
testResults.clear();
EventManager.get().post(new ServerTypeChangedEvent(serverType));
} else if(oldValue != null) {
oldValue.setSelected(true);
}
@ -626,7 +626,9 @@ public class ServerPreferencesController extends PreferencesDetailController {
@Subscribe
public void bwtSyncStatus(BwtSyncStatusEvent event) {
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));
connectionService.cancel();
}

View file

@ -159,3 +159,8 @@
.root .header-panel {
-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;
}