diff --git a/drongo b/drongo index 7ac4bce1..71b57782 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit 7ac4bce14f04163c57b94e34945b5e4a1bf79eb6 +Subproject commit 71b5778226ef22881240143425325525c1a98d06 diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index 52a11b9e..b3448942 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -1229,6 +1229,8 @@ public class AppController implements Initializable { } } } + + EventManager.get().post(new WalletOpenedEvent(storage, wallet)); } public WalletForm addWalletSubTab(TabPane subTabs, Storage storage, Wallet wallet, Wallet backupWallet) { diff --git a/src/main/java/com/sparrowwallet/sparrow/AppServices.java b/src/main/java/com/sparrowwallet/sparrow/AppServices.java index 89611bd7..b68312cc 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppServices.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppServices.java @@ -968,7 +968,10 @@ public class AppServices { @Subscribe public void walletOpening(WalletOpeningEvent event) { restartBwt(event.getWallet()); + } + @Subscribe + public void walletOpened(WalletOpenedEvent event) { String walletId = event.getStorage().getWalletId(event.getWallet()); Whirlpool whirlpool = whirlpoolMap.get(walletId); if(whirlpool != null && !whirlpool.isStarted() && isConnected()) { diff --git a/src/main/java/com/sparrowwallet/sparrow/MainApp.java b/src/main/java/com/sparrowwallet/sparrow/MainApp.java index 44b7d5e0..f91dfe44 100644 --- a/src/main/java/com/sparrowwallet/sparrow/MainApp.java +++ b/src/main/java/com/sparrowwallet/sparrow/MainApp.java @@ -42,7 +42,13 @@ public class MainApp extends Application { @Override public void init() throws Exception { - Thread.setDefaultUncaughtExceptionHandler((t, e) -> LoggerFactory.getLogger(MainApp.class).error("Exception in thread \"" + t.getName() + "\"", e)); + Thread.setDefaultUncaughtExceptionHandler((t, e) -> { + if(e instanceof IndexOutOfBoundsException && Arrays.stream(e.getStackTrace()).anyMatch(element -> element.getClassName().equals("javafx.scene.chart.BarChart"))) { + LoggerFactory.getLogger(MainApp.class).debug("Exception in thread \"" + t.getName() + "\"", e);; + } else { + LoggerFactory.getLogger(MainApp.class).error("Exception in thread \"" + t.getName() + "\"", e); + } + }); super.init(); } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/HelpLabel.java b/src/main/java/com/sparrowwallet/sparrow/control/HelpLabel.java index 4c176ee0..d27d26cd 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/HelpLabel.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/HelpLabel.java @@ -27,10 +27,10 @@ public class HelpLabel extends Label { } private static Glyph getHelpGlyph() { - Glyph lockGlyph = new Glyph("Font Awesome 5 Free Solid", FontAwesome5.Glyph.QUESTION_CIRCLE); - lockGlyph.getStyleClass().add("help-icon"); - lockGlyph.setFontSize(12); - return lockGlyph; + Glyph glyph = new Glyph("Font Awesome 5 Free Solid", FontAwesome5.Glyph.QUESTION_CIRCLE); + glyph.getStyleClass().add("help-icon"); + glyph.setFontSize(11); + return glyph; } public final StringProperty helpTextProperty() { diff --git a/src/main/java/com/sparrowwallet/sparrow/control/UtxosTreeTable.java b/src/main/java/com/sparrowwallet/sparrow/control/UtxosTreeTable.java index 611ca75a..5a6b43ad 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/UtxosTreeTable.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/UtxosTreeTable.java @@ -101,7 +101,9 @@ public class UtxosTreeTable extends CoinTreeTable { public void updateHistory(List updatedNodes) { //Utxo entries should have already been updated, so only a resort required - sort(); + if(!getRoot().getChildren().isEmpty()) { + sort(); + } } public void updateLabel(Entry entry) { diff --git a/src/main/java/com/sparrowwallet/sparrow/event/WalletOpenedEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/WalletOpenedEvent.java new file mode 100644 index 00000000..5bc4da39 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/event/WalletOpenedEvent.java @@ -0,0 +1,22 @@ +package com.sparrowwallet.sparrow.event; + +import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.sparrow.io.Storage; + +public class WalletOpenedEvent { + private final Storage storage; + private final Wallet wallet; + + public WalletOpenedEvent(Storage storage, Wallet wallet) { + this.storage = storage; + this.wallet = wallet; + } + + public Storage getStorage() { + return storage; + } + + public Wallet getWallet() { + return wallet; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java b/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java index 296e69b2..9be28b1c 100644 --- a/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java +++ b/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java @@ -39,6 +39,7 @@ public class FontAwesome5 extends GlyphFont { HAND_HOLDING_MEDICAL('\ue05c'), HAND_HOLDING_WATER('\uf4c1'), HISTORY('\uf1da'), + INFO_CIRCLE('\uf05a'), KEY('\uf084'), LAPTOP('\uf109'), LOCK('\uf023'), diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java index ca94aeb3..d1afe22e 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java @@ -128,7 +128,10 @@ public class SendController extends WalletFormController implements Initializabl private ToggleButton privacyToggle; @FXML - private HelpLabel privacyAnalysis; + private HelpLabel optimizationHelp; + + @FXML + private Label privacyAnalysis; @FXML private Button clearButton; @@ -417,6 +420,9 @@ public class SendController extends WalletFormController implements Initializabl }); setPreferredOptimizationStrategy(); updatePrivacyAnalysis(null); + optimizationHelp.managedProperty().bind(optimizationHelp.visibleProperty()); + privacyAnalysis.managedProperty().bind(privacyAnalysis.visibleProperty()); + optimizationHelp.visibleProperty().bind(privacyAnalysis.visibleProperty().not()); createButton.managedProperty().bind(createButton.visibleProperty()); premixButton.managedProperty().bind(premixButton.visibleProperty()); @@ -971,11 +977,15 @@ public class SendController extends WalletFormController implements Initializabl private void updatePrivacyAnalysis(WalletTransaction walletTransaction) { if(walletTransaction == null) { - privacyAnalysis.setHelpText("Determines whether to optimize the transaction for low fees or greater privacy"); - privacyAnalysis.setHelpGraphic(null); + privacyAnalysis.setVisible(false); + privacyAnalysis.setTooltip(null); } else { - privacyAnalysis.setHelpText(""); - privacyAnalysis.setHelpGraphic(new PrivacyAnalysisTooltip(walletTransaction)); + privacyAnalysis.setVisible(true); + Tooltip tooltip = new Tooltip(); + tooltip.setShowDelay(new Duration(50)); + tooltip.setShowDuration(Duration.INDEFINITE); + tooltip.setGraphic(new PrivacyAnalysisTooltip(walletTransaction)); + privacyAnalysis.setTooltip(tooltip); } } @@ -1012,6 +1022,9 @@ public class SendController extends WalletFormController implements Initializabl validationSupport.setErrorDecorationEnabled(false); setInputFieldsDisabled(false); + + premixButton.setVisible(false); + createButton.setDefaultButton(true); } public UtxoSelector getUtxoSelector() { @@ -1108,35 +1121,8 @@ public class SendController extends WalletFormController implements Initializabl } } - Wallet copy = getWalletForm().getWallet().copy(); - String walletId = walletForm.getWalletId(); - - if(copy.isEncrypted()) { - WalletPasswordDialog dlg = new WalletPasswordDialog(copy.getMasterName(), WalletPasswordDialog.PasswordRequirement.LOAD); - Optional password = dlg.showAndWait(); - if(password.isPresent()) { - Storage.DecryptWalletService decryptWalletService = new Storage.DecryptWalletService(copy, password.get()); - decryptWalletService.setOnSucceeded(workerStateEvent -> { - EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Done")); - Wallet decryptedWallet = decryptWalletService.getValue(); - broadcastPremixUnencrypted(decryptedWallet); - }); - decryptWalletService.setOnFailed(workerStateEvent -> { - EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Failed")); - AppServices.showErrorDialog("Incorrect Password", decryptWalletService.getException().getMessage()); - }); - EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.START, "Decrypting wallet...")); - decryptWalletService.start(); - } - } else { - broadcastPremixUnencrypted(copy); - } - } - - public void broadcastPremixUnencrypted(Wallet decryptedWallet) { + //The WhirlpoolWallet has already been configured for the tx0 preview Whirlpool whirlpool = AppServices.get().getWhirlpool(getWalletForm().getWalletId()); - whirlpool.setScode(Config.get().getScode()); - whirlpool.setHDWallet(getWalletForm().getWalletId(), decryptedWallet); Map utxos = walletTransactionProperty.get().getSelectedUtxos(); Whirlpool.Tx0BroadcastService tx0BroadcastService = new Whirlpool.Tx0BroadcastService(whirlpool, whirlpoolProperty.get(), utxos.keySet()); tx0BroadcastService.setOnRunning(workerStateEvent -> { @@ -1146,12 +1132,10 @@ public class SendController extends WalletFormController implements Initializabl tx0BroadcastService.setOnSucceeded(workerStateEvent -> { premixButton.setDisable(false); Sha256Hash txid = tx0BroadcastService.getValue(); - decryptedWallet.clearPrivate(); clear(null); }); tx0BroadcastService.setOnFailed(workerStateEvent -> { premixButton.setDisable(false); - decryptedWallet.clearPrivate(); Throwable exception = workerStateEvent.getSource().getException(); while(exception.getCause() != null) { exception = exception.getCause(); diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/UtxosController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/UtxosController.java index 82bfd0f3..9710b5fe 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/UtxosController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/UtxosController.java @@ -6,8 +6,10 @@ import com.samourai.whirlpool.client.tx0.Tx0Preview; import com.sparrowwallet.drongo.BitcoinUnit; import com.sparrowwallet.drongo.KeyPurpose; import com.sparrowwallet.drongo.Network; +import com.sparrowwallet.drongo.SecureString; import com.sparrowwallet.drongo.address.Address; import com.sparrowwallet.drongo.address.InvalidAddressException; +import com.sparrowwallet.drongo.crypto.*; import com.sparrowwallet.drongo.protocol.Transaction; import com.sparrowwallet.drongo.wallet.*; import com.sparrowwallet.sparrow.AppServices; @@ -15,6 +17,7 @@ import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.control.*; import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.io.Config; +import com.sparrowwallet.sparrow.io.Storage; import com.sparrowwallet.sparrow.whirlpool.Whirlpool; import com.sparrowwallet.sparrow.whirlpool.WhirlpoolDialog; import javafx.application.Platform; @@ -160,17 +163,70 @@ public class UtxosController extends WalletFormController implements Initializab List selectedEntries = getSelectedUtxos(); WhirlpoolDialog whirlpoolDialog = new WhirlpoolDialog(getWalletForm().getWalletId(), getWalletForm().getWallet(), selectedEntries); Optional optTx0Preview = whirlpoolDialog.showAndWait(); - optTx0Preview.ifPresent(tx0Preview -> previewPremixTransaction(getWalletForm().getWallet(), tx0Preview, selectedEntries)); + optTx0Preview.ifPresent(tx0Preview -> previewPremix(tx0Preview, selectedEntries)); } - public void previewPremixTransaction(Wallet wallet, Tx0Preview tx0Preview, List utxoEntries) { + public void previewPremix(Tx0Preview tx0Preview, List utxoEntries) { + Wallet wallet = getWalletForm().getWallet(); + String walletId = walletForm.getWalletId(); + + if(!wallet.isWhirlpoolMasterWallet() && wallet.isEncrypted()) { + WalletPasswordDialog dlg = new WalletPasswordDialog(wallet.getMasterName(), WalletPasswordDialog.PasswordRequirement.LOAD); + Optional password = dlg.showAndWait(); + if(password.isPresent()) { + Storage.KeyDerivationService keyDerivationService = new Storage.KeyDerivationService(walletForm.getStorage(), password.get()); + keyDerivationService.setOnSucceeded(workerStateEvent -> { + EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Done")); + ECKey encryptionFullKey = keyDerivationService.getValue(); + Key key = new Key(encryptionFullKey.getPrivKeyBytes(), walletForm.getStorage().getKeyDeriver().getSalt(), EncryptionType.Deriver.ARGON2); + wallet.decrypt(key); + + try { + prepareWhirlpoolWallet(wallet); + } finally { + wallet.encrypt(key); + for(Wallet childWallet : wallet.getChildWallets()) { + if(!childWallet.isEncrypted()) { + childWallet.encrypt(key); + } + } + key.clear(); + encryptionFullKey.clear(); + password.get().clear(); + } + + previewPremix(wallet, tx0Preview, utxoEntries); + }); + keyDerivationService.setOnFailed(workerStateEvent -> { + EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Failed")); + AppServices.showErrorDialog("Incorrect Password", keyDerivationService.getException().getMessage()); + }); + EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.START, "Decrypting wallet...")); + keyDerivationService.start(); + } + } else { + if(!wallet.isWhirlpoolMasterWallet()) { + prepareWhirlpoolWallet(wallet); + } + + previewPremix(wallet, tx0Preview, utxoEntries); + } + } + + private void prepareWhirlpoolWallet(Wallet decryptedWallet) { + Whirlpool whirlpool = AppServices.get().getWhirlpool(getWalletForm().getWalletId()); + whirlpool.setScode(Config.get().getScode()); + whirlpool.setHDWallet(getWalletForm().getWalletId(), decryptedWallet); + for(StandardAccount whirlpoolAccount : StandardAccount.WHIRLPOOL_ACCOUNTS) { - if(wallet.getChildWallet(whirlpoolAccount) == null) { - Wallet childWallet = wallet.addChildWallet(whirlpoolAccount); - EventManager.get().post(new ChildWalletAddedEvent(getWalletForm().getStorage(), wallet, childWallet)); + if(decryptedWallet.getChildWallet(whirlpoolAccount) == null) { + Wallet childWallet = decryptedWallet.addChildWallet(whirlpoolAccount); + EventManager.get().post(new ChildWalletAddedEvent(getWalletForm().getStorage(), decryptedWallet, childWallet)); } } + } + private void previewPremix(Wallet wallet, Tx0Preview tx0Preview, List utxoEntries) { Wallet premixWallet = wallet.getChildWallet(StandardAccount.WHIRLPOOL_PREMIX); Wallet badbankWallet = wallet.getChildWallet(StandardAccount.WHIRLPOOL_BADBANK); diff --git a/src/main/java/com/sparrowwallet/sparrow/whirlpool/Whirlpool.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/Whirlpool.java index e8c73ebc..f96d5051 100644 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/Whirlpool.java +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/Whirlpool.java @@ -67,7 +67,7 @@ public class Whirlpool { private HD_Wallet hdWallet; private String walletId; - private BooleanProperty mixingProperty = new SimpleBooleanProperty(false); + private final BooleanProperty mixingProperty = new SimpleBooleanProperty(false); public Whirlpool(Network network, HostAndPort torProxy, String sCode) { this.torProxy = torProxy; @@ -134,14 +134,8 @@ public class Whirlpool { } private Tx0ParamService getTx0ParamService() { - try { - SparrowMinerFeeSupplier minerFeeSupplier = SparrowMinerFeeSupplier.getInstance(); - return new Tx0ParamService(minerFeeSupplier, config); - } catch(Exception e) { - log.error("Error fetching miner fees", e); - } - - return null; + SparrowMinerFeeSupplier minerFeeSupplier = SparrowMinerFeeSupplier.getInstance(); + return new Tx0ParamService(minerFeeSupplier, config); } public void setHDWallet(String walletId, Wallet wallet) { diff --git a/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataPersister/SparrowDataPersister.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataPersister/SparrowDataPersister.java index 81aa7726..bca9abf8 100644 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataPersister/SparrowDataPersister.java +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataPersister/SparrowDataPersister.java @@ -9,8 +9,8 @@ import com.samourai.whirlpool.client.wallet.data.walletState.WalletStateSupplier import com.sparrowwallet.sparrow.whirlpool.dataSource.SparrowWalletStateSupplier; public class SparrowDataPersister implements DataPersister { - private WalletStateSupplier walletStateSupplier; - private UtxoConfigSupplier utxoConfigSupplier; + private final WalletStateSupplier walletStateSupplier; + private final UtxoConfigSupplier utxoConfigSupplier; public SparrowDataPersister(WhirlpoolWallet whirlpoolWallet) throws Exception { WhirlpoolWalletConfig config = whirlpoolWallet.getConfig(); diff --git a/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowIndexHandler.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowIndexHandler.java index 1737d7f2..a0aa8d4d 100644 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowIndexHandler.java +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowIndexHandler.java @@ -4,8 +4,8 @@ import com.samourai.wallet.client.indexHandler.AbstractIndexHandler; import com.sparrowwallet.drongo.wallet.WalletNode; public class SparrowIndexHandler extends AbstractIndexHandler { - private WalletNode walletNode; - private int defaultValue; + private final WalletNode walletNode; + private final int defaultValue; public SparrowIndexHandler(WalletNode walletNode) { this(walletNode, 0); @@ -19,8 +19,7 @@ public class SparrowIndexHandler extends AbstractIndexHandler { @Override public synchronized int get() { Integer currentIndex = walletNode.getHighestUsedIndex(); - int nextIndex = currentIndex == null ? defaultValue : currentIndex + 1; - return nextIndex; + return currentIndex == null ? defaultValue : currentIndex + 1; } @Override diff --git a/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowWalletStateSupplier.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowWalletStateSupplier.java index 6cf7c7d5..7e511488 100644 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowWalletStateSupplier.java +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowWalletStateSupplier.java @@ -15,13 +15,13 @@ import java.util.LinkedHashMap; import java.util.Map; public class SparrowWalletStateSupplier implements WalletStateSupplier { - private String walletId; - private Map indexHandlerWallets; + private final String walletId; + private final Map indexHandlerWallets; // private int externalIndexDefault; public SparrowWalletStateSupplier(String walletId, ExternalDestination externalDestination) throws Exception { this.walletId = walletId; - this.indexHandlerWallets = new LinkedHashMap(); + this.indexHandlerWallets = new LinkedHashMap<>(); // this.externalIndexDefault = externalDestination != null ? externalDestination.getStartIndex() : 0; } diff --git a/src/main/resources/com/sparrowwallet/sparrow/sql/V2__Whirlpool.sql b/src/main/resources/com/sparrowwallet/sparrow/sql/V2__Whirlpool.sql index 411d9389..c99d6760 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/sql/V2__Whirlpool.sql +++ b/src/main/resources/com/sparrowwallet/sparrow/sql/V2__Whirlpool.sql @@ -1,2 +1 @@ -drop table if exists utxoMixData; -create table utxoMixData (id identity not null, hash binary(32) not null, mixesDone integer not null default 0, expired bigint, wallet bigint not null); +create table utxoMixData (id identity not null, hash binary(32) not null, mixesDone integer not null default 0, expired bigint, wallet bigint not null); \ No newline at end of file diff --git a/src/main/resources/com/sparrowwallet/sparrow/wallet/send.fxml b/src/main/resources/com/sparrowwallet/sparrow/wallet/send.fxml index b05f914e..4442211b 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/wallet/send.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/wallet/send.fxml @@ -176,7 +176,12 @@ - + +