refactor to WhirlpoolServices

This commit is contained in:
Craig Raw 2021-09-02 12:37:07 +02:00
parent 2fc551e35b
commit e8af7c70bd
11 changed files with 205 additions and 169 deletions

View file

@ -898,7 +898,7 @@ public class AppController implements Initializable {
if(wallet.isWhirlpoolMasterWallet()) { if(wallet.isWhirlpoolMasterWallet()) {
String walletId = storage.getWalletId(wallet); String walletId = storage.getWalletId(wallet);
Whirlpool whirlpool = AppServices.get().getWhirlpool(walletId); Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(walletId);
whirlpool.setScode(wallet.getMasterMixConfig().getScode()); whirlpool.setScode(wallet.getMasterMixConfig().getScode());
whirlpool.setHDWallet(storage.getWalletId(wallet), copy); whirlpool.setHDWallet(storage.getWalletId(wallet), copy);
} }
@ -1187,7 +1187,7 @@ public class AppController implements Initializable {
tab.setContextMenu(getTabContextMenu(tab)); tab.setContextMenu(getTabContextMenu(tab));
tab.setClosable(true); tab.setClosable(true);
tab.setOnCloseRequest(event -> { tab.setOnCloseRequest(event -> {
if(AppServices.get().getWhirlpoolForMixToWallet(((WalletTabData)tab.getUserData()).getWalletForm().getWalletId()) != null) { if(AppServices.getWhirlpoolServices().getWhirlpoolForMixToWallet(((WalletTabData)tab.getUserData()).getWalletForm().getWalletId()) != null) {
Optional<ButtonType> 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); Optional<ButtonType> 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) { if(optType.isPresent() && optType.get() == ButtonType.NO) {
event.consume(); event.consume();

View file

@ -11,14 +11,13 @@ import com.sparrowwallet.drongo.protocol.Transaction;
import com.sparrowwallet.drongo.psbt.PSBT; import com.sparrowwallet.drongo.psbt.PSBT;
import com.sparrowwallet.drongo.uri.BitcoinURI; import com.sparrowwallet.drongo.uri.BitcoinURI;
import com.sparrowwallet.drongo.wallet.KeystoreSource; import com.sparrowwallet.drongo.wallet.KeystoreSource;
import com.sparrowwallet.drongo.wallet.MixConfig;
import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.control.TextUtils; import com.sparrowwallet.sparrow.control.TextUtils;
import com.sparrowwallet.sparrow.control.TrayManager; import com.sparrowwallet.sparrow.control.TrayManager;
import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.io.*; import com.sparrowwallet.sparrow.io.*;
import com.sparrowwallet.sparrow.net.*; import com.sparrowwallet.sparrow.net.*;
import com.sparrowwallet.sparrow.whirlpool.Whirlpool; import com.sparrowwallet.sparrow.whirlpool.WhirlpoolServices;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
@ -75,6 +74,8 @@ public class AppServices {
private static AppServices INSTANCE; private static AppServices INSTANCE;
private final WhirlpoolServices whirlpoolServices = new WhirlpoolServices();
private final MainApp application; private final MainApp application;
private final Map<Window, List<WalletTabData>> walletWindows = new LinkedHashMap<>(); private final Map<Window, List<WalletTabData>> walletWindows = new LinkedHashMap<>();
@ -93,8 +94,6 @@ public class AppServices {
private TorService torService; private TorService torService;
private final Map<String, Whirlpool> whirlpoolMap = new HashMap<>();
private static Integer currentBlockHeight; private static Integer currentBlockHeight;
private static BlockHeader latestBlockHeader; private static BlockHeader latestBlockHeader;
@ -143,6 +142,7 @@ public class AppServices {
public AppServices(MainApp application) { public AppServices(MainApp application) {
this.application = application; this.application = application;
EventManager.get().register(this); EventManager.get().register(this);
EventManager.get().register(whirlpoolServices);
} }
public void start() { public void start() {
@ -421,6 +421,10 @@ public class AppServices {
return INSTANCE; return INSTANCE;
} }
public static WhirlpoolServices getWhirlpoolServices() {
return get().whirlpoolServices;
}
public static AppController newAppWindow(Stage stage) { public static AppController newAppWindow(Stage stage) {
try { try {
FXMLLoader appLoader = new FXMLLoader(AppServices.class.getResource("app.fxml")); FXMLLoader appLoader = new FXMLLoader(AppServices.class.getResource("app.fxml"));
@ -453,80 +457,6 @@ public class AppServices {
return application; return application;
} }
public Whirlpool getWhirlpool(Wallet wallet) {
Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet();
for(List<WalletTabData> 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<String, Whirlpool> 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) { public void minimizeStage(Stage stage) {
if(trayManager == null) { if(trayManager == null) {
trayManager = new TrayManager(); trayManager = new TrayManager();
@ -867,12 +797,6 @@ public class AppServices {
addMempoolRateSizes(event.getMempoolRateSizes()); addMempoolRateSizes(event.getMempoolRateSizes());
minimumRelayFeeRate = event.getMinimumRelayFeeRate(); minimumRelayFeeRate = event.getMinimumRelayFeeRate();
latestBlockHeader = event.getBlockHeader(); latestBlockHeader = event.getBlockHeader();
startAllWhirlpool();
}
@Subscribe
public void disconnection(DisconnectionEvent event) {
shutdownAllWhirlpool();
} }
@Subscribe @Subscribe
@ -1001,71 +925,6 @@ public class AppServices {
restartBwt(event.getWallet()); 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) { private void restartBwt(Wallet wallet) {
if(Config.get().getServerType() == ServerType.BITCOIN_CORE && isConnected() && wallet.isValid()) { if(Config.get().getServerType() == ServerType.BITCOIN_CORE && isConnected() && wallet.isValid()) {
connectionService.cancel(); connectionService.cancel();

View file

@ -123,7 +123,7 @@ public class MixStatusCell extends TreeTableCell<Entry, UtxoEntry.MixStatus> {
private static class MixStatusContextMenu extends ContextMenu { private static class MixStatusContextMenu extends ContextMenu {
public MixStatusContextMenu(UtxoEntry utxoEntry, boolean isMixing) { public MixStatusContextMenu(UtxoEntry utxoEntry, boolean isMixing) {
Whirlpool pool = AppServices.get().getWhirlpool(utxoEntry.getWallet()); Whirlpool pool = AppServices.getWhirlpoolServices().getWhirlpool(utxoEntry.getWallet());
if(isMixing) { if(isMixing) {
MenuItem mixStop = new MenuItem("Stop Mixing"); MenuItem mixStop = new MenuItem("Stop Mixing");
if(pool != null) { if(pool != null) {
@ -132,7 +132,7 @@ public class MixStatusCell extends TreeTableCell<Entry, UtxoEntry.MixStatus> {
mixStop.setGraphic(getStopGlyph()); mixStop.setGraphic(getStopGlyph());
mixStop.setOnAction(event -> { mixStop.setOnAction(event -> {
hide(); hide();
Whirlpool whirlpool = AppServices.get().getWhirlpool(utxoEntry.getWallet()); Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(utxoEntry.getWallet());
if(whirlpool != null) { if(whirlpool != null) {
try { try {
whirlpool.mixStop(utxoEntry.getHashIndex()); whirlpool.mixStop(utxoEntry.getHashIndex());
@ -151,7 +151,7 @@ public class MixStatusCell extends TreeTableCell<Entry, UtxoEntry.MixStatus> {
mixNow.setGraphic(getMixGlyph()); mixNow.setGraphic(getMixGlyph());
mixNow.setOnAction(event -> { mixNow.setOnAction(event -> {
hide(); hide();
Whirlpool whirlpool = AppServices.get().getWhirlpool(utxoEntry.getWallet()); Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(utxoEntry.getWallet());
if(whirlpool != null) { if(whirlpool != null) {
try { try {
whirlpool.mix(utxoEntry.getHashIndex()); whirlpool.mix(utxoEntry.getHashIndex());

View file

@ -52,7 +52,7 @@ public class MixToController implements Initializable {
String mixToWalletId = null; String mixToWalletId = null;
try { try {
mixToWalletId = AppServices.get().getWhirlpoolMixToWalletId(mixConfig); mixToWalletId = AppServices.getWhirlpoolServices().getWhirlpoolMixToWalletId(mixConfig);
} catch(NoSuchElementException e) { } catch(NoSuchElementException e) {
//ignore, mix to wallet is not open //ignore, mix to wallet is not open
} }

View file

@ -28,7 +28,7 @@ public class MixToDialog extends Dialog<Boolean> {
MixToController mixToController = mixToLoader.getController(); MixToController mixToController = mixToLoader.getController();
mixToController.initializeView(wallet); 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 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); final ButtonType applyButtonType = new javafx.scene.control.ButtonType(whirlpool.isStarted() ? "Restart Whirlpool" : "Apply", ButtonBar.ButtonData.APPLY);
dialogPane.getButtonTypes().addAll(closeButtonType, applyButtonType); dialogPane.getButtonTypes().addAll(closeButtonType, applyButtonType);
@ -38,7 +38,7 @@ public class MixToDialog extends Dialog<Boolean> {
applyButton.setDefaultButton(true); applyButton.setDefaultButton(true);
try { try {
AppServices.get().getWhirlpoolMixToWalletId(wallet.getMasterMixConfig()); AppServices.getWhirlpoolServices().getWhirlpoolMixToWalletId(wallet.getMasterMixConfig());
} catch(NoSuchElementException e) { } catch(NoSuchElementException e) {
applyButton.setDisable(false); applyButton.setDisable(false);
} }

View file

@ -1123,7 +1123,7 @@ public class SendController extends WalletFormController implements Initializabl
} }
//The WhirlpoolWallet has already been configured for the tx0 preview //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<BlockTransactionHashIndex, WalletNode> utxos = walletTransactionProperty.get().getSelectedUtxos(); Map<BlockTransactionHashIndex, WalletNode> utxos = walletTransactionProperty.get().getSelectedUtxos();
Whirlpool.Tx0BroadcastService tx0BroadcastService = new Whirlpool.Tx0BroadcastService(whirlpool, whirlpoolProperty.get(), utxos.keySet()); Whirlpool.Tx0BroadcastService tx0BroadcastService = new Whirlpool.Tx0BroadcastService(whirlpool, whirlpoolProperty.get(), utxos.keySet());
tx0BroadcastService.setOnRunning(workerStateEvent -> { tx0BroadcastService.setOnRunning(workerStateEvent -> {

View file

@ -149,7 +149,7 @@ public class UtxoEntry extends HashIndexEntry {
return wallet.getUtxoMixData(getHashIndex()); return wallet.getUtxoMixData(getHashIndex());
} }
Whirlpool whirlpool = AppServices.get().getWhirlpool(wallet); Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(wallet);
if(whirlpool != null) { if(whirlpool != null) {
UtxoMixData utxoMixData = whirlpool.getMixData(getHashIndex()); UtxoMixData utxoMixData = whirlpool.getMixData(getHashIndex());
if(utxoMixData != null) { if(utxoMixData != null) {

View file

@ -107,7 +107,7 @@ public class UtxosController extends WalletFormController implements Initializab
mixTo.setVisible(getWalletForm().getWallet().getStandardAccountType() == StandardAccount.WHIRLPOOL_POSTMIX); mixTo.setVisible(getWalletForm().getWallet().getStandardAccountType() == StandardAccount.WHIRLPOOL_POSTMIX);
if(mixButtonsBox.isVisible()) { if(mixButtonsBox.isVisible()) {
Whirlpool whirlpool = AppServices.get().getWhirlpool(getWalletForm().getWallet()); Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(getWalletForm().getWallet());
if(whirlpool != null) { if(whirlpool != null) {
stopMix.visibleProperty().bind(whirlpool.mixingProperty()); stopMix.visibleProperty().bind(whirlpool.mixingProperty());
whirlpool.startingProperty().addListener(new WeakChangeListener<>(mixingStartingListener)); whirlpool.startingProperty().addListener(new WeakChangeListener<>(mixingStartingListener));
@ -166,7 +166,7 @@ public class UtxosController extends WalletFormController implements Initializab
if(mixConfig != null && mixConfig.getMixToWalletName() != null) { if(mixConfig != null && mixConfig.getMixToWalletName() != null) {
mixTo.setText("Mix to " + mixConfig.getMixToWalletName()); mixTo.setText("Mix to " + mixConfig.getMixToWalletName());
try { try {
AppServices.get().getWhirlpoolMixToWalletId(mixConfig); AppServices.getWhirlpoolServices().getWhirlpoolMixToWalletId(mixConfig);
mixTo.setGraphic(getExternalGlyph()); 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")); 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) { } catch(NoSuchElementException e) {
@ -248,7 +248,7 @@ public class UtxosController extends WalletFormController implements Initializab
} }
private void prepareWhirlpoolWallet(Wallet decryptedWallet) { 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.setScode(decryptedWallet.getMasterMixConfig().getScode());
whirlpool.setHDWallet(getWalletForm().getWalletId(), decryptedWallet); whirlpool.setHDWallet(getWalletForm().getWalletId(), decryptedWallet);
@ -311,7 +311,7 @@ public class UtxosController extends WalletFormController implements Initializab
startMix.setDisable(true); startMix.setDisable(true);
stopMix.setDisable(false); stopMix.setDisable(false);
Whirlpool whirlpool = AppServices.get().getWhirlpool(getWalletForm().getWallet()); Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(getWalletForm().getWallet());
if(whirlpool != null && !whirlpool.isStarted() && AppServices.isConnected()) { if(whirlpool != null && !whirlpool.isStarted() && AppServices.isConnected()) {
Whirlpool.StartupService startupService = new Whirlpool.StartupService(whirlpool); Whirlpool.StartupService startupService = new Whirlpool.StartupService(whirlpool);
startupService.setOnFailed(workerStateEvent -> { startupService.setOnFailed(workerStateEvent -> {
@ -329,7 +329,7 @@ public class UtxosController extends WalletFormController implements Initializab
stopMix.setDisable(true); stopMix.setDisable(true);
startMix.setDisable(false); startMix.setDisable(false);
Whirlpool whirlpool = AppServices.get().getWhirlpool(getWalletForm().getWallet()); Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(getWalletForm().getWallet());
if(whirlpool.isStarted()) { if(whirlpool.isStarted()) {
Whirlpool.ShutdownService shutdownService = new Whirlpool.ShutdownService(whirlpool); Whirlpool.ShutdownService shutdownService = new Whirlpool.ShutdownService(whirlpool);
shutdownService.setOnFailed(workerStateEvent -> { shutdownService.setOnFailed(workerStateEvent -> {
@ -350,11 +350,11 @@ public class UtxosController extends WalletFormController implements Initializab
MixToDialog mixToDialog = new MixToDialog(getWalletForm().getWallet()); MixToDialog mixToDialog = new MixToDialog(getWalletForm().getWallet());
Optional<Boolean> optApply = mixToDialog.showAndWait(); Optional<Boolean> optApply = mixToDialog.showAndWait();
if(optApply.isPresent() && optApply.get()) { if(optApply.isPresent() && optApply.get()) {
Whirlpool whirlpool = AppServices.get().getWhirlpool(getWalletForm().getWallet()); Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(getWalletForm().getWallet());
MixConfig mixConfig = getWalletForm().getWallet().getMasterMixConfig(); MixConfig mixConfig = getWalletForm().getWallet().getMasterMixConfig();
try { try {
String mixToWalletId = AppServices.get().getWhirlpoolMixToWalletId(mixConfig); String mixToWalletId = AppServices.getWhirlpoolServices().getWhirlpoolMixToWalletId(mixConfig);
whirlpool.setMixToWallet(mixToWalletId, mixConfig.getMinMixes()); whirlpool.setMixToWallet(mixToWalletId, mixConfig.getMinMixes());
} catch(NoSuchElementException e) { } catch(NoSuchElementException e) {
mixConfig.setMixToWalletName(null); mixConfig.setMixToWalletName(null);

View file

@ -39,7 +39,7 @@ public class WalletUtxosEntry extends Entry {
} }
protected void retrieveMixProgress() { protected void retrieveMixProgress() {
Whirlpool whirlpool = AppServices.get().getWhirlpool(getWallet()); Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(getWallet());
if(whirlpool != null) { if(whirlpool != null) {
for(Entry entry : getChildren()) { for(Entry entry : getChildren()) {
UtxoEntry utxoEntry = (UtxoEntry)entry; UtxoEntry utxoEntry = (UtxoEntry)entry;

View file

@ -194,7 +194,7 @@ public class WhirlpoolController {
private void fetchPools() { private void fetchPools() {
long totalUtxoValue = utxoEntries.stream().mapToLong(Entry::getValue).sum(); 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 -> { poolsService.setOnSucceeded(workerStateEvent -> {
List<Pool> availablePools = poolsService.getValue().stream().filter(pool1 -> totalUtxoValue >= (pool1.getPremixValueMin() + pool1.getFeeValue())).toList(); List<Pool> availablePools = poolsService.getValue().stream().filter(pool1 -> totalUtxoValue >= (pool1.getPremixValueMin() + pool1.getFeeValue())).toList();
if(availablePools.isEmpty()) { if(availablePools.isEmpty()) {
@ -235,7 +235,7 @@ public class WhirlpoolController {
EventManager.get().post(new WalletMixConfigChangedEvent(wallet)); EventManager.get().post(new WalletMixConfigChangedEvent(wallet));
} }
Whirlpool whirlpool = AppServices.get().getWhirlpool(walletId); Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(walletId);
whirlpool.setScode(mixConfig.getScode()); whirlpool.setScode(mixConfig.getScode());
Whirlpool.Tx0PreviewService tx0PreviewService = new Whirlpool.Tx0PreviewService(whirlpool, wallet, pool, utxoEntries); Whirlpool.Tx0PreviewService tx0PreviewService = new Whirlpool.Tx0PreviewService(whirlpool, wallet, pool, utxoEntries);

View file

@ -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<String, Whirlpool> whirlpoolMap = new HashMap<>();
public Whirlpool getWhirlpool(Wallet wallet) {
Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet();
for(Map.Entry<Wallet, Storage> 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<String, Whirlpool> 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();
}
}
}