From ce60fe3dc64599d253f2ec827c35878b1ff53781 Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Wed, 5 Aug 2020 13:58:54 +0200 Subject: [PATCH] extra logging, backup existing wallet first functionality --- .../sparrowwallet/sparrow/AppController.java | 3 ++ .../sparrow/control/WalletPasswordDialog.java | 13 +++++++- .../com/sparrowwallet/sparrow/io/Storage.java | 32 +++++++++++++++++++ .../transaction/HeadersController.java | 6 ++++ .../sparrow/wallet/SettingsController.java | 18 ++++++++++- .../sparrow/wallet/WalletForm.java | 4 +++ .../com/sparrowwallet/sparrow/general.css | 2 +- 7 files changed, 75 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index 89dc6948..58002175 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -430,6 +430,7 @@ public class AppController implements Initializable { showErrorDialog("Invalid QR Code", result.error); } if(result.exception != null) { + log.error("Error opening webcam", result.exception); showErrorDialog("Error opening webcam", result.exception.getMessage()); } } @@ -474,6 +475,7 @@ public class AppController implements Initializable { } } } catch(IOException e) { + log.error("Error saving transaction", e); AppController.showErrorDialog("Error saving transaction", "Cannot write to " + file.getAbsolutePath()); } } @@ -610,6 +612,7 @@ public class AppController implements Initializable { if(exception instanceof InvalidPasswordException) { showErrorDialog("Invalid Password", "The wallet password was invalid."); } else { + log.error("Error Opening Wallet", exception); showErrorDialog("Error Opening Wallet", exception.getMessage()); } }); diff --git a/src/main/java/com/sparrowwallet/sparrow/control/WalletPasswordDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/WalletPasswordDialog.java index 574c4014..3d806611 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/WalletPasswordDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/WalletPasswordDialog.java @@ -20,11 +20,13 @@ public class WalletPasswordDialog extends Dialog { private final PasswordRequirement requirement; private final CustomPasswordField password; private final CustomPasswordField passwordConfirm; + private final CheckBox backupExisting; public WalletPasswordDialog(PasswordRequirement requirement) { this.requirement = requirement; this.password = (CustomPasswordField)TextFields.createClearablePasswordField(); this.passwordConfirm = (CustomPasswordField)TextFields.createClearablePasswordField(); + this.backupExisting = new CheckBox("Backup existing wallet first"); final DialogPane dialogPane = getDialogPane(); setTitle("Wallet Password"); @@ -32,7 +34,7 @@ public class WalletPasswordDialog extends Dialog { dialogPane.getStylesheets().add(AppController.class.getResource("general.css").toExternalForm()); dialogPane.getButtonTypes().addAll(ButtonType.CANCEL); dialogPane.setPrefWidth(380); - dialogPane.setPrefHeight(250); + dialogPane.setPrefHeight(260); Glyph lock = new Glyph("FontAwesome", FontAwesome.Glyph.LOCK); lock.setFontSize(50); @@ -43,6 +45,11 @@ public class WalletPasswordDialog extends Dialog { content.getChildren().add(password); content.getChildren().add(passwordConfirm); + if(requirement == PasswordRequirement.UPDATE_EMPTY || requirement == PasswordRequirement.UPDATE_SET) { + content.getChildren().add(backupExisting); + backupExisting.setSelected(true); + } + dialogPane.setContent(content); ValidationSupport validationSupport = new ValidationSupport(); @@ -84,6 +91,10 @@ public class WalletPasswordDialog extends Dialog { setResultConverter(dialogButton -> dialogButton == okButtonType ? new SecureString(password.getText()) : null); } + public boolean isBackupExisting() { + return backupExisting.isSelected(); + } + public enum PasswordRequirement { LOAD("Please enter the wallet password:", "Unlock"), UPDATE_NEW("Add a password to the wallet?\nLeave empty for none:", "No Password"), diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Storage.java b/src/main/java/com/sparrowwallet/sparrow/io/Storage.java index cb694359..620cef20 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Storage.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Storage.java @@ -1,5 +1,6 @@ package com.sparrowwallet.sparrow.io; +import com.google.common.io.Files; import com.google.gson.*; import com.sparrowwallet.drongo.ExtendedKey; import com.sparrowwallet.drongo.SecureString; @@ -19,6 +20,8 @@ import java.lang.reflect.Type; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.util.*; import java.util.zip.*; @@ -27,8 +30,11 @@ import static com.sparrowwallet.drongo.crypto.Argon2KeyDeriver.SPRW1_PARAMETERS; public class Storage { public static final ECKey NO_PASSWORD_KEY = ECKey.fromPublicOnly(ECKey.fromPrivate(Utils.hexToBytes("885e5a09708a167ea356a252387aa7c4893d138d632e296df8fbf5c12798bd28"))); + private static final DateFormat BACKUP_DATE_FORMAT = new SimpleDateFormat("yyyyMMddHHmmss"); + public static final String SPARROW_DIR = ".sparrow"; public static final String WALLETS_DIR = "wallets"; + public static final String WALLETS_BACKUP_DIR = "backup"; public static final String HEADER_MAGIC_1 = "SPRW1"; private static final int BINARY_HEADER_LENGTH = 28; @@ -157,6 +163,23 @@ public class Storage { outputStream.write(encoded); } + public void backupWallet() throws IOException { + File backupDir = getWalletsBackupDir(); + + Date backupDate = new Date(); + String backupName = walletFile.getName(); + String dateSuffix = "-" + BACKUP_DATE_FORMAT.format(backupDate); + int lastDot = backupName.lastIndexOf('.'); + if(lastDot > 0) { + backupName = backupName.substring(0, lastDot) + dateSuffix + backupName.substring(lastDot); + } else { + backupName += dateSuffix; + } + + File backupFile = new File(backupDir, backupName); + Files.copy(walletFile, backupFile); + } + public ECKey getEncryptionPubKey() { return encryptionPubKey; } @@ -228,6 +251,15 @@ public class Storage { return new File(getWalletsDir(), walletName); } + public static File getWalletsBackupDir() { + File walletsBackupDir = new File(getWalletsDir(), WALLETS_BACKUP_DIR); + if(!walletsBackupDir.exists()) { + walletsBackupDir.mkdirs(); + } + + return walletsBackupDir; + } + public static File getWalletsDir() { File walletsDir = new File(getSparrowDir(), WALLETS_DIR); if(!walletsDir.exists()) { diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java index 0fe8c8b0..709a0063 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java @@ -26,6 +26,8 @@ import javafx.scene.layout.VBox; import javafx.stage.FileChooser; import javafx.stage.Stage; import org.controlsfx.glyphfont.Glyph; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import tornadofx.control.DateTimePicker; import tornadofx.control.Field; import tornadofx.control.Fieldset; @@ -43,6 +45,7 @@ import java.util.*; import java.util.stream.Collectors; public class HeadersController extends TransactionFormController implements Initializable { + private static final Logger log = LoggerFactory.getLogger(HeadersController.class); public static final String LOCKTIME_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; public static final String BLOCK_TIMESTAMP_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss ZZZ"; public static final String UNFINALIZED_TXID_CLASS = "unfinalized-txid"; @@ -561,6 +564,7 @@ public class HeadersController extends TransactionFormController implements Init writer.print(headersForm.getPsbt().toBase64String()); } } catch(IOException e) { + log.error("Error saving PSBT", e); AppController.showErrorDialog("Error saving PSBT", "Cannot write to " + file.getAbsolutePath()); } } @@ -617,6 +621,7 @@ public class HeadersController extends TransactionFormController implements Init unencryptedWallet.sign(headersForm.getPsbt()); updateSignedKeystores(headersForm.getSigningWallet()); } catch(Exception e) { + log.warn("Failed to Sign", e); AppController.showErrorDialog("Failed to Sign", e.getMessage()); } } @@ -707,6 +712,7 @@ public class HeadersController extends TransactionFormController implements Init writer.print(Utils.bytesToHex(finalTx.bitcoinSerialize())); } } catch(IOException e) { + log.error("Error saving transaction", e); AppController.showErrorDialog("Error saving transaction", "Cannot write to " + file.getAbsolutePath()); } } diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java index e7a44c67..ac017b9a 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java @@ -17,7 +17,6 @@ import com.sparrowwallet.sparrow.control.WalletPasswordDialog; import com.sparrowwallet.sparrow.event.SettingsChangedEvent; import com.sparrowwallet.sparrow.event.StorageEvent; import com.sparrowwallet.sparrow.event.TimedEvent; -import com.sparrowwallet.sparrow.event.WalletSettingsChangedEvent; import com.sparrowwallet.sparrow.io.Storage; import javafx.beans.property.SimpleIntegerProperty; import javafx.collections.FXCollections; @@ -28,6 +27,8 @@ import javafx.scene.control.*; import javafx.scene.layout.StackPane; import org.controlsfx.control.RangeSlider; import org.controlsfx.tools.Borders; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import tornadofx.control.Fieldset; import java.io.IOException; @@ -39,6 +40,7 @@ import java.util.ResourceBundle; import java.util.stream.Collectors; public class SettingsController extends WalletFormController implements Initializable { + private static final Logger log = LoggerFactory.getLogger(SettingsController.class); @FXML private ComboBox policyType; @@ -272,11 +274,24 @@ public class SettingsController extends WalletFormController implements Initiali WalletPasswordDialog dlg = new WalletPasswordDialog(requirement); Optional password = dlg.showAndWait(); if(password.isPresent()) { + if(dlg.isBackupExisting()) { + try { + walletForm.saveBackup(); + } catch(IOException e) { + log.error("Error saving wallet backup", e); + AppController.showErrorDialog("Error saving wallet backup", e.getMessage()); + revert.setDisable(false); + apply.setDisable(false); + return; + } + } + if(password.get().length() == 0) { try { walletForm.getStorage().setEncryptionPubKey(Storage.NO_PASSWORD_KEY); walletForm.saveAndRefresh(); } catch (IOException e) { + log.error("Error saving wallet", e); AppController.showErrorDialog("Error saving wallet", e.getMessage()); revert.setDisable(false); apply.setDisable(false); @@ -304,6 +319,7 @@ public class SettingsController extends WalletFormController implements Initiali walletForm.getStorage().setEncryptionPubKey(encryptionPubKey); walletForm.saveAndRefresh(); } catch (Exception e) { + log.error("Error saving wallet", e); AppController.showErrorDialog("Error saving wallet", e.getMessage()); revert.setDisable(false); apply.setDisable(false); diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java index 581c0240..0401177f 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java @@ -59,6 +59,10 @@ public class WalletForm { refreshHistory(AppController.getCurrentBlockHeight()); } + public void saveBackup() throws IOException { + storage.backupWallet(); + } + public void refreshHistory(Integer blockHeight) { Wallet previousWallet = wallet.copy(); if(wallet.isValid() && AppController.isOnline()) { diff --git a/src/main/resources/com/sparrowwallet/sparrow/general.css b/src/main/resources/com/sparrowwallet/sparrow/general.css index 5a959d18..d447c478 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/general.css +++ b/src/main/resources/com/sparrowwallet/sparrow/general.css @@ -6,7 +6,7 @@ } .form .wideLabelFieldSet.fieldset:horizontal .label-container { - -fx-pref-width: 160px; + -fx-pref-width: 170px; -fx-pref-height: 25px; }