diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index e724fb13..ade0dac5 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -898,7 +898,7 @@ public class AppController implements Initializable { if(wallet.isWhirlpoolMasterWallet()) { String walletId = storage.getWalletId(wallet); - Whirlpool whirlpool = AppServices.get().getWhirlpool(walletId); + Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(walletId); whirlpool.setScode(wallet.getMasterMixConfig().getScode()); whirlpool.setHDWallet(storage.getWalletId(wallet), copy); } @@ -1187,7 +1187,7 @@ public class AppController implements Initializable { tab.setContextMenu(getTabContextMenu(tab)); tab.setClosable(true); tab.setOnCloseRequest(event -> { - if(AppServices.get().getWhirlpoolForMixToWallet(((WalletTabData)tab.getUserData()).getWalletForm().getWalletId()) != null) { + if(AppServices.getWhirlpoolServices().getWhirlpoolForMixToWallet(((WalletTabData)tab.getUserData()).getWalletForm().getWalletId()) != null) { Optional optType = AppServices.showWarningDialog("Close mix to wallet?", "This wallet has been configured as the final destination for mixes, and needs to be open for this to occur.\n\nAre you sure you want to close?", ButtonType.NO, ButtonType.YES); if(optType.isPresent() && optType.get() == ButtonType.NO) { event.consume(); diff --git a/src/main/java/com/sparrowwallet/sparrow/AppServices.java b/src/main/java/com/sparrowwallet/sparrow/AppServices.java index f2494433..535ab430 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppServices.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppServices.java @@ -11,14 +11,13 @@ import com.sparrowwallet.drongo.protocol.Transaction; import com.sparrowwallet.drongo.psbt.PSBT; import com.sparrowwallet.drongo.uri.BitcoinURI; import com.sparrowwallet.drongo.wallet.KeystoreSource; -import com.sparrowwallet.drongo.wallet.MixConfig; import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.sparrow.control.TextUtils; import com.sparrowwallet.sparrow.control.TrayManager; import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.io.*; import com.sparrowwallet.sparrow.net.*; -import com.sparrowwallet.sparrow.whirlpool.Whirlpool; +import com.sparrowwallet.sparrow.whirlpool.WhirlpoolServices; import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; @@ -75,6 +74,8 @@ public class AppServices { private static AppServices INSTANCE; + private final WhirlpoolServices whirlpoolServices = new WhirlpoolServices(); + private final MainApp application; private final Map> walletWindows = new LinkedHashMap<>(); @@ -93,8 +94,6 @@ public class AppServices { private TorService torService; - private final Map whirlpoolMap = new HashMap<>(); - private static Integer currentBlockHeight; private static BlockHeader latestBlockHeader; @@ -143,6 +142,7 @@ public class AppServices { public AppServices(MainApp application) { this.application = application; EventManager.get().register(this); + EventManager.get().register(whirlpoolServices); } public void start() { @@ -421,6 +421,10 @@ public class AppServices { return INSTANCE; } + public static WhirlpoolServices getWhirlpoolServices() { + return get().whirlpoolServices; + } + public static AppController newAppWindow(Stage stage) { try { FXMLLoader appLoader = new FXMLLoader(AppServices.class.getResource("app.fxml")); @@ -453,80 +457,6 @@ public class AppServices { return application; } - public Whirlpool getWhirlpool(Wallet wallet) { - Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet(); - for(List walletTabDataList : walletWindows.values()) { - for(WalletTabData walletTabData : walletTabDataList) { - if(walletTabData.getWallet() == masterWallet) { - return whirlpoolMap.get(walletTabData.getWalletForm().getWalletId()); - } - } - } - - return null; - } - - public Whirlpool getWhirlpool(String walletId) { - Whirlpool whirlpool = whirlpoolMap.get(walletId); - if(whirlpool == null) { - HostAndPort torProxy = AppServices.isTorRunning() ? HostAndPort.fromParts("localhost", TorService.PROXY_PORT) : (Config.get().getProxyServer() == null || Config.get().getProxyServer().isEmpty() || !Config.get().isUseProxy() ? null : HostAndPort.fromString(Config.get().getProxyServer())); - whirlpool = new Whirlpool(Network.get(), torProxy); - whirlpoolMap.put(walletId, whirlpool); - } - - return whirlpool; - } - - private void startAllWhirlpool() { - for(Map.Entry entry : whirlpoolMap.entrySet().stream().filter(entry -> entry.getValue().hasWallet() && !entry.getValue().isStarted()).collect(Collectors.toList())) { - Wallet wallet = getWallet(entry.getKey()); - Whirlpool whirlpool = entry.getValue(); - startWhirlpool(wallet, whirlpool); - } - } - - private void startWhirlpool(Wallet wallet, Whirlpool whirlpool) { - if(wallet.getMasterMixConfig().getMixOnStartup() != Boolean.FALSE) { - try { - String mixToWalletId = getWhirlpoolMixToWalletId(wallet.getMasterMixConfig()); - whirlpool.setMixToWallet(mixToWalletId, wallet.getMasterMixConfig().getMinMixes()); - } catch(NoSuchElementException e) { - showWarningDialog("Mix to wallet not open", wallet.getName() + " is configured to mix to " + wallet.getMasterMixConfig().getMixToWalletName() + ", but this wallet is not open. Mix to wallets are required to be open to avoid address reuse."); - } - - Whirlpool.StartupService startupService = new Whirlpool.StartupService(whirlpool); - startupService.setOnFailed(workerStateEvent -> { - log.error("Failed to start whirlpool", workerStateEvent.getSource().getException()); - }); - startupService.start(); - } - } - - private void shutdownAllWhirlpool() { - for(Whirlpool whirlpool : whirlpoolMap.values().stream().filter(Whirlpool::isStarted).collect(Collectors.toList())) { - Whirlpool.ShutdownService shutdownService = new Whirlpool.ShutdownService(whirlpool); - shutdownService.setOnFailed(workerStateEvent -> { - log.error("Failed to shutdown whirlpool", workerStateEvent.getSource().getException()); - }); - shutdownService.start(); - } - } - - public String getWhirlpoolMixToWalletId(MixConfig mixConfig) { - if(mixConfig == null || mixConfig.getMixToWalletFile() == null || mixConfig.getMixToWalletName() == null) { - return null; - } - - return getOpenWallets().entrySet().stream() - .filter(entry -> entry.getValue().getWalletFile().equals(mixConfig.getMixToWalletFile()) && entry.getKey().getName().equals(mixConfig.getMixToWalletName())) - .map(entry -> entry.getValue().getWalletId(entry.getKey())) - .findFirst().orElseThrow(); - } - - public Whirlpool getWhirlpoolForMixToWallet(String walletId) { - return whirlpoolMap.values().stream().filter(whirlpool -> walletId.equals(whirlpool.getMixToWalletId())).findFirst().orElse(null); - } - public void minimizeStage(Stage stage) { if(trayManager == null) { trayManager = new TrayManager(); @@ -867,12 +797,6 @@ public class AppServices { addMempoolRateSizes(event.getMempoolRateSizes()); minimumRelayFeeRate = event.getMinimumRelayFeeRate(); latestBlockHeader = event.getBlockHeader(); - startAllWhirlpool(); - } - - @Subscribe - public void disconnection(DisconnectionEvent event) { - shutdownAllWhirlpool(); } @Subscribe @@ -1001,71 +925,6 @@ public class AppServices { 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()) { - startWhirlpool(event.getWallet(), whirlpool); - } - - Whirlpool mixFromWhirlpool = whirlpoolMap.entrySet().stream().filter(entry -> event.getStorage().getWalletFile().equals(getWallet(entry.getKey()).getMasterMixConfig().getMixToWalletFile())).map(Map.Entry::getValue).findFirst().orElse(null); - if(mixFromWhirlpool != null) { - mixFromWhirlpool.setMixToWallet(walletId, getWallet(mixFromWhirlpool.getWalletId()).getMasterMixConfig().getMinMixes()); - if(mixFromWhirlpool.isStarted()) { - Whirlpool.RestartService restartService = new Whirlpool.RestartService(mixFromWhirlpool); - restartService.setOnFailed(workerStateEvent -> { - log.error("Failed to restart whirlpool", workerStateEvent.getSource().getException()); - }); - restartService.start(); - } - } - } - - @Subscribe - public void walletTabsClosed(WalletTabsClosedEvent event) { - for(WalletTabData walletTabData : event.getClosedWalletTabData()) { - String walletId = walletTabData.getStorage().getWalletId(walletTabData.getWallet()); - Whirlpool whirlpool = whirlpoolMap.remove(walletId); - if(whirlpool != null) { - if(whirlpool.isStarted()) { - Whirlpool.ShutdownService shutdownService = new Whirlpool.ShutdownService(whirlpool); - shutdownService.setOnSucceeded(workerStateEvent -> { - WhirlpoolEventService.getInstance().unregister(whirlpool); - }); - shutdownService.setOnFailed(workerStateEvent -> { - log.error("Failed to shutdown whirlpool", workerStateEvent.getSource().getException()); - }); - shutdownService.start(); - } else { - //Ensure http clients are shutdown - whirlpool.shutdown(); - WhirlpoolEventService.getInstance().unregister(whirlpool); - } - } - - Whirlpool mixToWhirlpool = getWhirlpoolForMixToWallet(walletId); - if(mixToWhirlpool != null && event.getClosedWalletTabData().stream().noneMatch(walletTabData1 -> walletTabData1.getWalletForm().getWalletId().equals(mixToWhirlpool.getWalletId()))) { - mixToWhirlpool.setMixToWallet(null, null); - if(mixToWhirlpool.isStarted()) { - Whirlpool.RestartService restartService = new Whirlpool.RestartService(mixToWhirlpool); - restartService.setOnFailed(workerStateEvent -> { - log.error("Failed to restart whirlpool", workerStateEvent.getSource().getException()); - }); - restartService.start(); - } - } - } - } - - @Subscribe - public void walletHistoryChanged(WalletHistoryChangedEvent event) { - Whirlpool whirlpool = getWhirlpool(event.getWallet()); - if(whirlpool != null) { - whirlpool.refreshUtxos(); - } - } - private void restartBwt(Wallet wallet) { if(Config.get().getServerType() == ServerType.BITCOIN_CORE && isConnected() && wallet.isValid()) { connectionService.cancel(); diff --git a/src/main/java/com/sparrowwallet/sparrow/control/MixStatusCell.java b/src/main/java/com/sparrowwallet/sparrow/control/MixStatusCell.java index 8ed3ce11..a405289d 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/MixStatusCell.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/MixStatusCell.java @@ -123,7 +123,7 @@ public class MixStatusCell extends TreeTableCell { private static class MixStatusContextMenu extends ContextMenu { public MixStatusContextMenu(UtxoEntry utxoEntry, boolean isMixing) { - Whirlpool pool = AppServices.get().getWhirlpool(utxoEntry.getWallet()); + Whirlpool pool = AppServices.getWhirlpoolServices().getWhirlpool(utxoEntry.getWallet()); if(isMixing) { MenuItem mixStop = new MenuItem("Stop Mixing"); if(pool != null) { @@ -132,7 +132,7 @@ public class MixStatusCell extends TreeTableCell { mixStop.setGraphic(getStopGlyph()); mixStop.setOnAction(event -> { hide(); - Whirlpool whirlpool = AppServices.get().getWhirlpool(utxoEntry.getWallet()); + Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(utxoEntry.getWallet()); if(whirlpool != null) { try { whirlpool.mixStop(utxoEntry.getHashIndex()); @@ -151,7 +151,7 @@ public class MixStatusCell extends TreeTableCell { mixNow.setGraphic(getMixGlyph()); mixNow.setOnAction(event -> { hide(); - Whirlpool whirlpool = AppServices.get().getWhirlpool(utxoEntry.getWallet()); + Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(utxoEntry.getWallet()); if(whirlpool != null) { try { whirlpool.mix(utxoEntry.getHashIndex()); diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/MixToController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/MixToController.java index 63d483ee..b2225085 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/MixToController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/MixToController.java @@ -52,7 +52,7 @@ public class MixToController implements Initializable { String mixToWalletId = null; try { - mixToWalletId = AppServices.get().getWhirlpoolMixToWalletId(mixConfig); + mixToWalletId = AppServices.getWhirlpoolServices().getWhirlpoolMixToWalletId(mixConfig); } catch(NoSuchElementException e) { //ignore, mix to wallet is not open } diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/MixToDialog.java b/src/main/java/com/sparrowwallet/sparrow/wallet/MixToDialog.java index 7617a56f..75345a3c 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/MixToDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/MixToDialog.java @@ -28,7 +28,7 @@ public class MixToDialog extends Dialog { MixToController mixToController = mixToLoader.getController(); mixToController.initializeView(wallet); - Whirlpool whirlpool = AppServices.get().getWhirlpool(wallet); + Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(wallet); final ButtonType closeButtonType = new javafx.scene.control.ButtonType("Close", ButtonBar.ButtonData.CANCEL_CLOSE); final ButtonType applyButtonType = new javafx.scene.control.ButtonType(whirlpool.isStarted() ? "Restart Whirlpool" : "Apply", ButtonBar.ButtonData.APPLY); dialogPane.getButtonTypes().addAll(closeButtonType, applyButtonType); @@ -38,7 +38,7 @@ public class MixToDialog extends Dialog { applyButton.setDefaultButton(true); try { - AppServices.get().getWhirlpoolMixToWalletId(wallet.getMasterMixConfig()); + AppServices.getWhirlpoolServices().getWhirlpoolMixToWalletId(wallet.getMasterMixConfig()); } catch(NoSuchElementException e) { applyButton.setDisable(false); } diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java index c466e638..3be1f674 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java @@ -1123,7 +1123,7 @@ public class SendController extends WalletFormController implements Initializabl } //The WhirlpoolWallet has already been configured for the tx0 preview - Whirlpool whirlpool = AppServices.get().getWhirlpool(getWalletForm().getStorage().getWalletId(masterWallet)); + Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(getWalletForm().getStorage().getWalletId(masterWallet)); Map utxos = walletTransactionProperty.get().getSelectedUtxos(); Whirlpool.Tx0BroadcastService tx0BroadcastService = new Whirlpool.Tx0BroadcastService(whirlpool, whirlpoolProperty.get(), utxos.keySet()); tx0BroadcastService.setOnRunning(workerStateEvent -> { diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/UtxoEntry.java b/src/main/java/com/sparrowwallet/sparrow/wallet/UtxoEntry.java index 0c25b350..f68f7549 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/UtxoEntry.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/UtxoEntry.java @@ -149,7 +149,7 @@ public class UtxoEntry extends HashIndexEntry { return wallet.getUtxoMixData(getHashIndex()); } - Whirlpool whirlpool = AppServices.get().getWhirlpool(wallet); + Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(wallet); if(whirlpool != null) { UtxoMixData utxoMixData = whirlpool.getMixData(getHashIndex()); if(utxoMixData != null) { diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/UtxosController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/UtxosController.java index b61497ad..7abd84a5 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/UtxosController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/UtxosController.java @@ -107,7 +107,7 @@ public class UtxosController extends WalletFormController implements Initializab mixTo.setVisible(getWalletForm().getWallet().getStandardAccountType() == StandardAccount.WHIRLPOOL_POSTMIX); if(mixButtonsBox.isVisible()) { - Whirlpool whirlpool = AppServices.get().getWhirlpool(getWalletForm().getWallet()); + Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(getWalletForm().getWallet()); if(whirlpool != null) { stopMix.visibleProperty().bind(whirlpool.mixingProperty()); whirlpool.startingProperty().addListener(new WeakChangeListener<>(mixingStartingListener)); @@ -166,7 +166,7 @@ public class UtxosController extends WalletFormController implements Initializab if(mixConfig != null && mixConfig.getMixToWalletName() != null) { mixTo.setText("Mix to " + mixConfig.getMixToWalletName()); try { - AppServices.get().getWhirlpoolMixToWalletId(mixConfig); + AppServices.getWhirlpoolServices().getWhirlpoolMixToWalletId(mixConfig); mixTo.setGraphic(getExternalGlyph()); mixTo.setTooltip(new Tooltip("Mixing to " + mixConfig.getMixToWalletName() + " after at least " + (mixConfig.getMinMixes() == null ? Whirlpool.DEFAULT_MIXTO_MIN_MIXES : mixConfig.getMinMixes()) + " mixes")); } catch(NoSuchElementException e) { @@ -248,7 +248,7 @@ public class UtxosController extends WalletFormController implements Initializab } private void prepareWhirlpoolWallet(Wallet decryptedWallet) { - Whirlpool whirlpool = AppServices.get().getWhirlpool(getWalletForm().getWalletId()); + Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(getWalletForm().getWalletId()); whirlpool.setScode(decryptedWallet.getMasterMixConfig().getScode()); whirlpool.setHDWallet(getWalletForm().getWalletId(), decryptedWallet); @@ -311,7 +311,7 @@ public class UtxosController extends WalletFormController implements Initializab startMix.setDisable(true); stopMix.setDisable(false); - Whirlpool whirlpool = AppServices.get().getWhirlpool(getWalletForm().getWallet()); + Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(getWalletForm().getWallet()); if(whirlpool != null && !whirlpool.isStarted() && AppServices.isConnected()) { Whirlpool.StartupService startupService = new Whirlpool.StartupService(whirlpool); startupService.setOnFailed(workerStateEvent -> { @@ -329,7 +329,7 @@ public class UtxosController extends WalletFormController implements Initializab stopMix.setDisable(true); startMix.setDisable(false); - Whirlpool whirlpool = AppServices.get().getWhirlpool(getWalletForm().getWallet()); + Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(getWalletForm().getWallet()); if(whirlpool.isStarted()) { Whirlpool.ShutdownService shutdownService = new Whirlpool.ShutdownService(whirlpool); shutdownService.setOnFailed(workerStateEvent -> { @@ -350,11 +350,11 @@ public class UtxosController extends WalletFormController implements Initializab MixToDialog mixToDialog = new MixToDialog(getWalletForm().getWallet()); Optional optApply = mixToDialog.showAndWait(); if(optApply.isPresent() && optApply.get()) { - Whirlpool whirlpool = AppServices.get().getWhirlpool(getWalletForm().getWallet()); + Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(getWalletForm().getWallet()); MixConfig mixConfig = getWalletForm().getWallet().getMasterMixConfig(); try { - String mixToWalletId = AppServices.get().getWhirlpoolMixToWalletId(mixConfig); + String mixToWalletId = AppServices.getWhirlpoolServices().getWhirlpoolMixToWalletId(mixConfig); whirlpool.setMixToWallet(mixToWalletId, mixConfig.getMinMixes()); } catch(NoSuchElementException e) { mixConfig.setMixToWalletName(null); diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletUtxosEntry.java b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletUtxosEntry.java index c243c883..e6916ac1 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletUtxosEntry.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletUtxosEntry.java @@ -39,7 +39,7 @@ public class WalletUtxosEntry extends Entry { } protected void retrieveMixProgress() { - Whirlpool whirlpool = AppServices.get().getWhirlpool(getWallet()); + Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(getWallet()); if(whirlpool != null) { for(Entry entry : getChildren()) { UtxoEntry utxoEntry = (UtxoEntry)entry; diff --git a/src/main/java/com/sparrowwallet/sparrow/whirlpool/WhirlpoolController.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/WhirlpoolController.java index b5d5200f..f767a63b 100644 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/WhirlpoolController.java +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/WhirlpoolController.java @@ -194,7 +194,7 @@ public class WhirlpoolController { private void fetchPools() { long totalUtxoValue = utxoEntries.stream().mapToLong(Entry::getValue).sum(); - Whirlpool.PoolsService poolsService = new Whirlpool.PoolsService(AppServices.get().getWhirlpool(walletId)); + Whirlpool.PoolsService poolsService = new Whirlpool.PoolsService(AppServices.getWhirlpoolServices().getWhirlpool(walletId)); poolsService.setOnSucceeded(workerStateEvent -> { List availablePools = poolsService.getValue().stream().filter(pool1 -> totalUtxoValue >= (pool1.getPremixValueMin() + pool1.getFeeValue())).toList(); if(availablePools.isEmpty()) { @@ -235,7 +235,7 @@ public class WhirlpoolController { EventManager.get().post(new WalletMixConfigChangedEvent(wallet)); } - Whirlpool whirlpool = AppServices.get().getWhirlpool(walletId); + Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(walletId); whirlpool.setScode(mixConfig.getScode()); Whirlpool.Tx0PreviewService tx0PreviewService = new Whirlpool.Tx0PreviewService(whirlpool, wallet, pool, utxoEntries); diff --git a/src/main/java/com/sparrowwallet/sparrow/whirlpool/WhirlpoolServices.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/WhirlpoolServices.java new file mode 100644 index 00000000..724abda2 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/WhirlpoolServices.java @@ -0,0 +1,177 @@ +package com.sparrowwallet.sparrow.whirlpool; + +import com.google.common.eventbus.Subscribe; +import com.google.common.net.HostAndPort; +import com.samourai.whirlpool.client.wallet.WhirlpoolEventService; +import com.sparrowwallet.drongo.Network; +import com.sparrowwallet.drongo.wallet.MixConfig; +import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.sparrow.AppServices; +import com.sparrowwallet.sparrow.WalletTabData; +import com.sparrowwallet.sparrow.event.*; +import com.sparrowwallet.sparrow.io.Config; +import com.sparrowwallet.sparrow.io.Storage; +import com.sparrowwallet.sparrow.net.TorService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.stream.Collectors; + +public class WhirlpoolServices { + private static final Logger log = LoggerFactory.getLogger(WhirlpoolServices.class); + + private final Map whirlpoolMap = new HashMap<>(); + + public Whirlpool getWhirlpool(Wallet wallet) { + Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet(); + for(Map.Entry entry : AppServices.get().getOpenWallets().entrySet()) { + if(entry.getKey() == masterWallet) { + return whirlpoolMap.get(entry.getValue().getWalletId(entry.getKey())); + } + } + + return null; + } + + public Whirlpool getWhirlpool(String walletId) { + Whirlpool whirlpool = whirlpoolMap.get(walletId); + if(whirlpool == null) { + HostAndPort torProxy = AppServices.isTorRunning() ? HostAndPort.fromParts("localhost", TorService.PROXY_PORT) : (Config.get().getProxyServer() == null || Config.get().getProxyServer().isEmpty() || !Config.get().isUseProxy() ? null : HostAndPort.fromString(Config.get().getProxyServer())); + whirlpool = new Whirlpool(Network.get(), torProxy); + whirlpoolMap.put(walletId, whirlpool); + } + + return whirlpool; + } + + private void startAllWhirlpool() { + for(Map.Entry entry : whirlpoolMap.entrySet().stream().filter(entry -> entry.getValue().hasWallet() && !entry.getValue().isStarted()).collect(Collectors.toList())) { + Wallet wallet = AppServices.get().getWallet(entry.getKey()); + Whirlpool whirlpool = entry.getValue(); + startWhirlpool(wallet, whirlpool); + } + } + + private void startWhirlpool(Wallet wallet, Whirlpool whirlpool) { + if(wallet.getMasterMixConfig().getMixOnStartup() != Boolean.FALSE) { + try { + String mixToWalletId = getWhirlpoolMixToWalletId(wallet.getMasterMixConfig()); + whirlpool.setMixToWallet(mixToWalletId, wallet.getMasterMixConfig().getMinMixes()); + } catch(NoSuchElementException e) { + AppServices.showWarningDialog("Mix to wallet not open", wallet.getName() + " is configured to mix to " + wallet.getMasterMixConfig().getMixToWalletName() + ", but this wallet is not open. Mix to wallets are required to be open to avoid address reuse."); + } + + Whirlpool.StartupService startupService = new Whirlpool.StartupService(whirlpool); + startupService.setOnFailed(workerStateEvent -> { + log.error("Failed to start whirlpool", workerStateEvent.getSource().getException()); + }); + startupService.start(); + } + } + + private void shutdownAllWhirlpool() { + for(Whirlpool whirlpool : whirlpoolMap.values().stream().filter(Whirlpool::isStarted).collect(Collectors.toList())) { + Whirlpool.ShutdownService shutdownService = new Whirlpool.ShutdownService(whirlpool); + shutdownService.setOnFailed(workerStateEvent -> { + log.error("Failed to shutdown whirlpool", workerStateEvent.getSource().getException()); + }); + shutdownService.start(); + } + } + + public String getWhirlpoolMixToWalletId(MixConfig mixConfig) { + if(mixConfig == null || mixConfig.getMixToWalletFile() == null || mixConfig.getMixToWalletName() == null) { + return null; + } + + return AppServices.get().getOpenWallets().entrySet().stream() + .filter(entry -> entry.getValue().getWalletFile().equals(mixConfig.getMixToWalletFile()) && entry.getKey().getName().equals(mixConfig.getMixToWalletName())) + .map(entry -> entry.getValue().getWalletId(entry.getKey())) + .findFirst().orElseThrow(); + } + + public Whirlpool getWhirlpoolForMixToWallet(String walletId) { + return whirlpoolMap.values().stream().filter(whirlpool -> walletId.equals(whirlpool.getMixToWalletId())).findFirst().orElse(null); + } + + @Subscribe + public void newConnection(ConnectionEvent event) { + startAllWhirlpool(); + } + + @Subscribe + public void disconnection(DisconnectionEvent event) { + shutdownAllWhirlpool(); + } + + @Subscribe + public void walletOpened(WalletOpenedEvent event) { + String walletId = event.getStorage().getWalletId(event.getWallet()); + Whirlpool whirlpool = whirlpoolMap.get(walletId); + if(whirlpool != null && !whirlpool.isStarted() && AppServices.isConnected()) { + startWhirlpool(event.getWallet(), whirlpool); + } + + Whirlpool mixFromWhirlpool = whirlpoolMap.entrySet().stream() + .filter(entry -> event.getStorage().getWalletFile().equals(AppServices.get().getWallet(entry.getKey()).getMasterMixConfig().getMixToWalletFile())) + .map(Map.Entry::getValue).findFirst().orElse(null); + + if(mixFromWhirlpool != null) { + mixFromWhirlpool.setMixToWallet(walletId, AppServices.get().getWallet(mixFromWhirlpool.getWalletId()).getMasterMixConfig().getMinMixes()); + if(mixFromWhirlpool.isStarted()) { + Whirlpool.RestartService restartService = new Whirlpool.RestartService(mixFromWhirlpool); + restartService.setOnFailed(workerStateEvent -> { + log.error("Failed to restart whirlpool", workerStateEvent.getSource().getException()); + }); + restartService.start(); + } + } + } + + @Subscribe + public void walletTabsClosed(WalletTabsClosedEvent event) { + for(WalletTabData walletTabData : event.getClosedWalletTabData()) { + String walletId = walletTabData.getStorage().getWalletId(walletTabData.getWallet()); + Whirlpool whirlpool = whirlpoolMap.remove(walletId); + if(whirlpool != null) { + if(whirlpool.isStarted()) { + Whirlpool.ShutdownService shutdownService = new Whirlpool.ShutdownService(whirlpool); + shutdownService.setOnSucceeded(workerStateEvent -> { + WhirlpoolEventService.getInstance().unregister(whirlpool); + }); + shutdownService.setOnFailed(workerStateEvent -> { + log.error("Failed to shutdown whirlpool", workerStateEvent.getSource().getException()); + }); + shutdownService.start(); + } else { + //Ensure http clients are shutdown + whirlpool.shutdown(); + WhirlpoolEventService.getInstance().unregister(whirlpool); + } + } + + Whirlpool mixToWhirlpool = getWhirlpoolForMixToWallet(walletId); + if(mixToWhirlpool != null && event.getClosedWalletTabData().stream().noneMatch(walletTabData1 -> walletTabData1.getWalletForm().getWalletId().equals(mixToWhirlpool.getWalletId()))) { + mixToWhirlpool.setMixToWallet(null, null); + if(mixToWhirlpool.isStarted()) { + Whirlpool.RestartService restartService = new Whirlpool.RestartService(mixToWhirlpool); + restartService.setOnFailed(workerStateEvent -> { + log.error("Failed to restart whirlpool", workerStateEvent.getSource().getException()); + }); + restartService.start(); + } + } + } + } + + @Subscribe + public void walletHistoryChanged(WalletHistoryChangedEvent event) { + Whirlpool whirlpool = getWhirlpool(event.getWallet()); + if(whirlpool != null) { + whirlpool.refreshUtxos(); + } + } +}