From ff7a35e68b22fc96ac124bd969812e70ac1b9b51 Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Tue, 29 Sep 2020 15:45:22 +0200 Subject: [PATCH] select alternative network, support testnet on hwi, other fixes --- drongo | 2 +- sparrow.bat | 7 +++ sparrow.sh | 11 +++++ .../sparrowwallet/sparrow/AppController.java | 13 +++++- .../java/com/sparrowwallet/sparrow/Args.java | 15 ++++++ .../com/sparrowwallet/sparrow/MainApp.java | 46 ++++++++++++++++--- .../com/sparrowwallet/sparrow/io/Config.java | 7 ++- .../com/sparrowwallet/sparrow/io/Hwi.java | 13 +++++- .../com/sparrowwallet/sparrow/io/Storage.java | 14 ++++++ .../sparrow/wallet/SendController.java | 12 +++-- .../sparrow/wallet/SettingsController.java | 7 +++ src/main/java/module-info.java | 1 + 12 files changed, 133 insertions(+), 15 deletions(-) create mode 100644 sparrow.bat create mode 100755 sparrow.sh create mode 100644 src/main/java/com/sparrowwallet/sparrow/Args.java diff --git a/drongo b/drongo index 747bfa91..b877e94c 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit 747bfa915f1ecf743b5e8876b9a4c54062e57c94 +Subproject commit b877e94cd09adb3fbc17ecba95897ae5c428ffe9 diff --git a/sparrow.bat b/sparrow.bat new file mode 100644 index 00000000..2d3919d0 --- /dev/null +++ b/sparrow.bat @@ -0,0 +1,7 @@ +set ARGS=%* + +if "%ARGS%" != "" ( + gradlew.bat run --args="%ARGS%" +) else ( + gradlew.bat run +) diff --git a/sparrow.sh b/sparrow.sh new file mode 100755 index 00000000..632ef8c5 --- /dev/null +++ b/sparrow.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env sh + +args="$*" +args="${args%"${args##*[![:space:]]}"}" + +if [ -n "$args" ] +then + ./gradlew run --args="$args" +else + ./gradlew run +fi diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index 3262b102..10b0c389 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -4,6 +4,7 @@ import com.google.common.base.Charsets; import com.google.common.eventbus.Subscribe; import com.google.common.io.ByteSource; import com.sparrowwallet.drongo.BitcoinUnit; +import com.sparrowwallet.drongo.Network; import com.sparrowwallet.drongo.SecureString; import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.crypto.InvalidPasswordException; @@ -770,6 +771,7 @@ public class AppController implements Initializable { FileType fileType = IOUtils.getFileType(file); if(FileType.JSON.equals(fileType)) { Wallet wallet = storage.loadWallet(); + checkWalletNetwork(wallet); restorePublicKeysFromSeed(wallet, null); Tab tab = addWalletTab(storage, wallet); tabs.getSelectionModel().select(tab); @@ -786,10 +788,11 @@ public class AppController implements Initializable { EventManager.get().post(new StorageEvent(storage.getWalletFile(), TimedEvent.Action.END, "Done")); Storage.WalletAndKey walletAndKey = loadWalletService.getValue(); try { + checkWalletNetwork(walletAndKey.wallet); restorePublicKeysFromSeed(walletAndKey.wallet, walletAndKey.key); Tab tab = addWalletTab(storage, walletAndKey.wallet); tabs.getSelectionModel().select(tab); - } catch(MnemonicException e) { + } catch(Exception e) { showErrorDialog("Error Opening Wallet", e.getMessage()); } finally { walletAndKey.key.clear(); @@ -816,6 +819,12 @@ public class AppController implements Initializable { } } + private void checkWalletNetwork(Wallet wallet) { + if(wallet.getNetwork() != null && wallet.getNetwork() != Network.get()) { + throw new IllegalStateException("Provided " + wallet.getNetwork() + " wallet is invalid on a " + Network.get() + " network. Use a " + wallet.getNetwork() + " configuration to load this wallet."); + } + } + private void restorePublicKeysFromSeed(Wallet wallet, Key key) throws MnemonicException { if(wallet.containsSeeds()) { //Derive xpub and master fingerprint from seed, potentially with passphrase @@ -1382,7 +1391,7 @@ public class AppController implements Initializable { @Subscribe public void openWallets(OpenWalletsEvent event) { - List walletFiles = event.getWalletsMap().values().stream().map(storage -> storage.getWalletFile()).collect(Collectors.toList()); + List walletFiles = event.getWalletsMap().values().stream().map(Storage::getWalletFile).collect(Collectors.toList()); Config.get().setRecentWalletFiles(walletFiles); boolean usbWallet = false; diff --git a/src/main/java/com/sparrowwallet/sparrow/Args.java b/src/main/java/com/sparrowwallet/sparrow/Args.java new file mode 100644 index 00000000..751ff3f4 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/Args.java @@ -0,0 +1,15 @@ +package com.sparrowwallet.sparrow; + +import com.beust.jcommander.Parameter; +import com.sparrowwallet.drongo.Network; + +public class Args { + @Parameter(names = { "--dir", "-d" }, description = "Path to Sparrow home folder") + public String dir; + + @Parameter(names = { "--network", "-n" }, description = "Network to use (mainnet, testnet or regtest)") + public Network network; + + @Parameter(names = { "--help", "-h" }, description = "Show usage", help = true) + public boolean help; +} diff --git a/src/main/java/com/sparrowwallet/sparrow/MainApp.java b/src/main/java/com/sparrowwallet/sparrow/MainApp.java index f84ebf74..b2c1fcb2 100644 --- a/src/main/java/com/sparrowwallet/sparrow/MainApp.java +++ b/src/main/java/com/sparrowwallet/sparrow/MainApp.java @@ -1,5 +1,7 @@ package com.sparrowwallet.sparrow; +import com.beust.jcommander.JCommander; +import com.sparrowwallet.drongo.Network; import com.sparrowwallet.sparrow.control.WelcomeDialog; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5Brands; @@ -17,18 +19,20 @@ import javafx.scene.image.Image; import javafx.scene.text.Font; import javafx.stage.Stage; import org.controlsfx.glyphfont.GlyphFontRegistry; +import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; public class MainApp extends Application { + private static final Logger log = LoggerFactory.getLogger(MainApp.class); + public static final String APP_NAME = "Sparrow"; public static final String APP_VERSION = "0.9.4"; + public static final String APP_HOME_PROPERTY = "sparrow.home"; + public static final String NETWORK_ENV_PROPERTY = "SPARROW_NETWORK"; private Stage mainStage; @@ -107,7 +111,37 @@ public class MainApp extends Application { mainStage.close(); } - public static void main(String[] args) { - com.sun.javafx.application.LauncherImpl.launchApplication(MainApp.class, MainAppPreloader.class, args); + public static void main(String[] argv) { + Args args = new Args(); + JCommander jCommander = JCommander.newBuilder().addObject(args).programName(APP_NAME.toLowerCase()).acceptUnknownOptions(true).build(); + jCommander.parse(argv); + if(args.help) { + jCommander.usage(); + System.exit(0); + } + + if(args.dir != null) { + log.info("Using configured Sparrow home folder of " + args.dir); + System.setProperty(APP_HOME_PROPERTY, args.dir); + } + + if(args.network != null) { + Network.set(args.network); + } else { + String envNetwork = System.getenv(NETWORK_ENV_PROPERTY); + if(envNetwork != null) { + try { + Network.set(Network.valueOf(envNetwork.toUpperCase())); + } catch(Exception e) { + log.warn("Invalid " + NETWORK_ENV_PROPERTY + " property: " + envNetwork); + } + } + } + + if(Network.get() != Network.MAINNET) { + log.info("Using " + Network.get() + " configuration"); + } + + com.sun.javafx.application.LauncherImpl.launchApplication(MainApp.class, MainAppPreloader.class, argv); } } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Config.java b/src/main/java/com/sparrowwallet/sparrow/io/Config.java index d44db84b..9a69cd06 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Config.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Config.java @@ -44,7 +44,12 @@ public class Config { } private static File getConfigFile() { - return new File(Storage.getSparrowDir(), CONFIG_FILENAME); + File sparrowDir = Storage.getSparrowDir(); + if(!sparrowDir.exists()) { + sparrowDir.mkdirs(); + } + + return new File(sparrowDir, CONFIG_FILENAME); } private static Config load() { diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Hwi.java b/src/main/java/com/sparrowwallet/sparrow/io/Hwi.java index afc19291..a05475b1 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Hwi.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Hwi.java @@ -3,6 +3,7 @@ package com.sparrowwallet.sparrow.io; import com.google.common.io.ByteStreams; import com.google.common.io.CharStreams; import com.google.gson.*; +import com.sparrowwallet.drongo.Network; import com.sparrowwallet.drongo.protocol.ScriptType; import com.sparrowwallet.drongo.psbt.PSBT; import com.sparrowwallet.drongo.psbt.PSBTParseException; @@ -322,17 +323,27 @@ public class Hwi { } private List getDeviceCommand(Device device, Command command) throws IOException { - return List.of(getHwiExecutable(command).getAbsolutePath(), "--device-path", device.getPath(), "--device-type", device.getType(), command.toString()); + List elements = new ArrayList<>(List.of(getHwiExecutable(command).getAbsolutePath(), "--device-path", device.getPath(), "--device-type", device.getType(), command.toString())); + if(Network.get() != Network.MAINNET) { + elements.add(elements.size() - 1, "--testnet"); + } + return elements; } private List getDeviceCommand(Device device, Command command, String... commandData) throws IOException { List elements = new ArrayList<>(List.of(getHwiExecutable(command).getAbsolutePath(), "--device-path", device.getPath(), "--device-type", device.getType(), command.toString())); + if(Network.get() != Network.MAINNET) { + elements.add(elements.size() - 1, "--testnet"); + } elements.addAll(Arrays.stream(commandData).filter(Objects::nonNull).collect(Collectors.toList())); return elements; } private List getDeviceCommand(Device device, String passphrase, Command command, String... commandData) throws IOException { List elements = new ArrayList<>(List.of(getHwiExecutable(command).getAbsolutePath(), "--device-path", device.getPath(), "--device-type", device.getType(), "--password", passphrase, command.toString())); + if(Network.get() != Network.MAINNET) { + elements.add(elements.size() - 1, "--testnet"); + } elements.addAll(Arrays.stream(commandData).filter(Objects::nonNull).collect(Collectors.toList())); return elements; } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Storage.java b/src/main/java/com/sparrowwallet/sparrow/io/Storage.java index 58aab090..dca88db5 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Storage.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Storage.java @@ -3,6 +3,7 @@ package com.sparrowwallet.sparrow.io; import com.google.common.io.Files; import com.google.gson.*; import com.sparrowwallet.drongo.ExtendedKey; +import com.sparrowwallet.drongo.Network; import com.sparrowwallet.drongo.SecureString; import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.crypto.*; @@ -12,6 +13,7 @@ import com.sparrowwallet.drongo.wallet.Keystore; import com.sparrowwallet.drongo.wallet.MnemonicException; import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.drongo.wallet.WalletNode; +import com.sparrowwallet.sparrow.MainApp; import javafx.concurrent.Service; import javafx.concurrent.Task; import org.controlsfx.tools.Platform; @@ -272,6 +274,18 @@ public class Storage { } static File getSparrowDir() { + if(Network.get() != Network.MAINNET) { + return new File(getSparrowHome(), Network.get().getName()); + } + + return getSparrowHome(); + } + + static File getSparrowHome() { + if(System.getProperty(MainApp.APP_HOME_PROPERTY) != null) { + return new File(System.getProperty(MainApp.APP_HOME_PROPERTY)); + } + if(Platform.getCurrent() == Platform.WINDOWS) { return new File(getHomeDir(), WINDOWS_SPARROW_DIR); } diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java index 6693fd1c..1e3adadb 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java @@ -97,6 +97,8 @@ public class SendController extends WalletFormController implements Initializabl private final ObjectProperty walletTransactionProperty = new SimpleObjectProperty<>(null); + private final ObjectProperty createdWalletTransactionProperty = new SimpleObjectProperty<>(null); + private final BooleanProperty insufficientInputsProperty = new SimpleBooleanProperty(false); private final ChangeListener amountListener = new ChangeListener<>() { @@ -570,6 +572,7 @@ public class SendController extends WalletFormController implements Initializabl utxoSelectorProperty.setValue(null); utxoFilterProperty.setValue(null); walletTransactionProperty.setValue(null); + createdWalletTransactionProperty.set(null); validationSupport.setErrorDecorationEnabled(false); } @@ -583,6 +586,7 @@ public class SendController extends WalletFormController implements Initializabl } public void createTransaction(ActionEvent event) { + createdWalletTransactionProperty.set(walletTransactionProperty.get()); PSBT psbt = walletTransactionProperty.get().createPSBT(); EventManager.get().post(new ViewPSBTEvent(label.getText(), psbt)); } @@ -596,8 +600,8 @@ public class SendController extends WalletFormController implements Initializabl @Subscribe public void walletHistoryChanged(WalletHistoryChangedEvent event) { - if(event.getWallet().equals(walletForm.getWallet())) { - if(walletTransactionProperty.get() != null && walletTransactionProperty.get().getSelectedUtxos() != null && allSelectedUtxosSpent(event.getHistoryChangedNodes())) { + if(event.getWallet().equals(walletForm.getWallet()) && createdWalletTransactionProperty.get() != null) { + if(createdWalletTransactionProperty.get().getSelectedUtxos() != null && allSelectedUtxosSpent(event.getHistoryChangedNodes())) { clear(null); } else { updateTransaction(); @@ -606,9 +610,9 @@ public class SendController extends WalletFormController implements Initializabl } private boolean allSelectedUtxosSpent(List historyChangedNodes) { - Set unspentUtxos = new HashSet<>(walletTransactionProperty.get().getSelectedUtxos().keySet()); + Set unspentUtxos = new HashSet<>(createdWalletTransactionProperty.get().getSelectedUtxos().keySet()); - for(Map.Entry selectedUtxoEntry : walletTransactionProperty.get().getSelectedUtxos().entrySet()) { + for(Map.Entry selectedUtxoEntry : createdWalletTransactionProperty.get().getSelectedUtxos().entrySet()) { BlockTransactionHashIndex utxo = selectedUtxoEntry.getKey(); WalletNode utxoWalletNode = selectedUtxoEntry.getValue(); diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java index b9fdc5ca..980addcf 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java @@ -17,6 +17,7 @@ import com.sparrowwallet.sparrow.control.CopyableLabel; import com.sparrowwallet.sparrow.control.DescriptorArea; import com.sparrowwallet.sparrow.control.TextAreaDialog; import com.sparrowwallet.sparrow.control.WalletPasswordDialog; +import com.sparrowwallet.sparrow.event.RequestOpenWalletsEvent; import com.sparrowwallet.sparrow.event.SettingsChangedEvent; import com.sparrowwallet.sparrow.event.StorageEvent; import com.sparrowwallet.sparrow.event.TimedEvent; @@ -347,6 +348,9 @@ public class SettingsController extends WalletFormController implements Initiali try { walletForm.getStorage().setEncryptionPubKey(Storage.NO_PASSWORD_KEY); walletForm.saveAndRefresh(); + if(requirement == WalletPasswordDialog.PasswordRequirement.UPDATE_NEW) { + EventManager.get().post(new RequestOpenWalletsEvent()); + } } catch (IOException e) { log.error("Error saving wallet", e); AppController.showErrorDialog("Error saving wallet", e.getMessage()); @@ -381,6 +385,9 @@ public class SettingsController extends WalletFormController implements Initiali walletForm.getStorage().setEncryptionPubKey(encryptionPubKey); walletForm.saveAndRefresh(); + if(requirement == WalletPasswordDialog.PasswordRequirement.UPDATE_NEW) { + EventManager.get().post(new RequestOpenWalletsEvent()); + } } catch (Exception e) { log.error("Error saving wallet", e); AppController.showErrorDialog("Error saving wallet", e.getMessage()); diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index ed170d7f..38421894 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -22,5 +22,6 @@ open module com.sparrowwallet.sparrow { requires webcam.capture; requires netlayer.jpms; requires centerdevice.nsmenufx; + requires jcommander; requires slf4j.api; } \ No newline at end of file