diff --git a/src/main/java/com/sparrowwallet/sparrow/control/MixStatusCell.java b/src/main/java/com/sparrowwallet/sparrow/control/MixStatusCell.java index f15d57a2..37aa35b0 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/MixStatusCell.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/MixStatusCell.java @@ -10,8 +10,10 @@ import com.sparrowwallet.sparrow.wallet.Entry; import com.sparrowwallet.sparrow.wallet.UtxoEntry; import com.sparrowwallet.sparrow.whirlpool.Whirlpool; import com.sparrowwallet.sparrow.whirlpool.WhirlpoolException; +import javafx.animation.FadeTransition; import javafx.geometry.Pos; import javafx.scene.control.*; +import javafx.util.Duration; import org.controlsfx.glyphfont.Glyph; import org.controlsfx.tools.Platform; @@ -46,7 +48,7 @@ public class MixStatusCell extends TreeTableCell { } else if(mixStatus.getMixFailReason() != null) { setMixFail(mixStatus.getMixFailReason(), mixStatus.getMixError()); } else if(mixStatus.getMixProgress() != null) { - setMixProgress(mixStatus.getMixProgress()); + setMixProgress(mixStatus.getUtxoEntry(), mixStatus.getMixProgress()); } else { setGraphic(null); setTooltip(null); @@ -65,13 +67,28 @@ public class MixStatusCell extends TreeTableCell { private void setMixFail(MixFailReason mixFailReason, String mixError) { if(mixFailReason != MixFailReason.CANCEL) { - setGraphic(getFailGlyph()); + if(getGraphic() != null && getGraphic().getUserData() == mixFailReason) { + //Fade transition already set + return; + } + + Glyph failGlyph = getFailGlyph(); + setGraphic(failGlyph); Tooltip tt = new Tooltip(); tt.setText(mixFailReason.getMessage() + (mixError == null ? "" : ": " + mixError) + "\nMix failures are generally caused by peers disconnecting during a mix." + "\nMake sure your internet connection is stable and the computer is configured to prevent sleeping." + "\nTo prevent sleeping, use the " + getPlatformSleepConfig() + " or enable the function in the Tools menu."); setTooltip(tt); + + FadeTransition ft = new FadeTransition(Duration.hours(1), failGlyph); + ft.setFromValue(1); + ft.setToValue(0); + ft.setOnFinished(event -> { + setTooltip(null); + }); + ft.play(); + failGlyph.setUserData(mixFailReason); } else { setGraphic(null); setTooltip(null); @@ -89,14 +106,28 @@ public class MixStatusCell extends TreeTableCell { return "system power settings"; } - private void setMixProgress(MixProgress mixProgress) { + private void setMixProgress(UtxoEntry utxoEntry, MixProgress mixProgress) { if(mixProgress.getMixStep() != MixStep.FAIL) { ProgressIndicator progressIndicator = getProgressIndicator(); progressIndicator.setProgress(mixProgress.getMixStep().getProgressPercent() == 100 ? -1 : mixProgress.getMixStep().getProgressPercent() / 100.0); setGraphic(progressIndicator); Tooltip tt = new Tooltip(); - tt.setText(mixProgress.getMixStep().getMessage().substring(0, 1).toUpperCase() + mixProgress.getMixStep().getMessage().substring(1)); + String status = mixProgress.getMixStep().getMessage().substring(0, 1).toUpperCase() + mixProgress.getMixStep().getMessage().substring(1); + tt.setText(status); setTooltip(tt); + + if(mixProgress.getMixStep() == MixStep.REGISTERED_INPUT) { + tt.setOnShowing(event -> { + Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(utxoEntry.getWallet()); + Whirlpool.RegisteredInputsService registeredInputsService = new Whirlpool.RegisteredInputsService(whirlpool, mixProgress.getPoolId()); + registeredInputsService.setOnSucceeded(eventStateHandler -> { + if(registeredInputsService.getValue() != null) { + tt.setText(status + " (1 of " + registeredInputsService.getValue() + ")"); + } + }); + registeredInputsService.start(); + }); + } } else { setGraphic(null); setTooltip(null); diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Config.java b/src/main/java/com/sparrowwallet/sparrow/io/Config.java index dead0fcd..8515d2f9 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Config.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Config.java @@ -1,6 +1,7 @@ package com.sparrowwallet.sparrow.io; import com.google.gson.*; +import com.samourai.whirlpool.client.wallet.beans.IndexRange; import com.sparrowwallet.drongo.BitcoinUnit; import com.sparrowwallet.sparrow.Mode; import com.sparrowwallet.sparrow.Theme; @@ -40,9 +41,10 @@ public class Config { private boolean openWalletsInNewWindows = false; private boolean hideEmptyUsedAddresses = false; private boolean showTransactionHex = true; - private boolean showLoadingLog = false; + private boolean showLoadingLog = true; private boolean showUtxosChart = true; private boolean preventSleep = false; + private IndexRange postmixIndexRange = IndexRange.FULL; private List recentWalletFiles; private Integer keyDerivationPeriod; private File hwi; @@ -288,6 +290,15 @@ public class Config { this.preventSleep = preventSleep; } + public IndexRange getPostmixIndexRange() { + return postmixIndexRange; + } + + public void setPostmixIndexRange(IndexRange postmixIndexRange) { + this.postmixIndexRange = postmixIndexRange; + flush(); + } + public List getRecentWalletFiles() { return recentWalletFiles; } diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java index 03797016..bfb0d831 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java @@ -4,7 +4,6 @@ import com.google.common.eventbus.Subscribe; import com.samourai.whirlpool.client.whirlpool.beans.Pool; import com.sparrowwallet.drongo.BitcoinUnit; import com.sparrowwallet.drongo.KeyPurpose; -import com.sparrowwallet.drongo.SecureString; import com.sparrowwallet.drongo.address.InvalidAddressException; import com.sparrowwallet.drongo.protocol.Sha256Hash; import com.sparrowwallet.drongo.protocol.Transaction; diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/UtxoEntry.java b/src/main/java/com/sparrowwallet/sparrow/wallet/UtxoEntry.java index e989c4c9..a1ffe25f 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/UtxoEntry.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/UtxoEntry.java @@ -4,6 +4,7 @@ import com.samourai.whirlpool.client.mix.listener.MixFailReason; import com.samourai.whirlpool.client.mix.listener.MixStep; import com.samourai.whirlpool.client.wallet.beans.MixProgress; import com.samourai.whirlpool.protocol.beans.Utxo; +import com.sparrowwallet.drongo.KeyPurpose; import com.sparrowwallet.drongo.address.Address; import com.sparrowwallet.drongo.wallet.*; import com.sparrowwallet.sparrow.AppServices; @@ -151,12 +152,12 @@ public class UtxoEntry extends HashIndexEntry { return wallet.getUtxoMixData(getHashIndex()); } + //Mix data not available - recount (and store if WhirlpoolWallet is running) Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(wallet); - if(whirlpool != null) { - UtxoMixData utxoMixData = whirlpool.getMixData(getHashIndex()); - if(utxoMixData != null) { - return utxoMixData; - } + if(whirlpool != null && getUtxoEntry().getWallet().getStandardAccountType() == StandardAccount.WHIRLPOOL_POSTMIX && node.getKeyPurpose() == KeyPurpose.RECEIVE) { + int mixesDone = whirlpool.recountMixesDone(getUtxoEntry().getWallet(), getHashIndex()); + whirlpool.setMixesDone(getHashIndex(), mixesDone); + return new UtxoMixData(mixesDone, null); } return new UtxoMixData(getUtxoEntry().getWallet().getStandardAccountType() == StandardAccount.WHIRLPOOL_POSTMIX ? 1 : 0, null); diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/UtxosController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/UtxosController.java index 4f8bc091..b0847fa5 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/UtxosController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/UtxosController.java @@ -502,6 +502,10 @@ public class UtxosController extends WalletFormController implements Initializab utxosTable.updateHistoryStatus(event); } + @Subscribe + public void newBlock(NewBlockEvent event) { + getWalletForm().getWalletUtxosEntry().updateMixProgress(); + } @Subscribe public void bwtSyncStatus(BwtSyncStatusEvent event) { diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletUtxosEntry.java b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletUtxosEntry.java index e6916ac1..a7339922 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletUtxosEntry.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletUtxosEntry.java @@ -12,7 +12,7 @@ public class WalletUtxosEntry extends Entry { public WalletUtxosEntry(Wallet wallet) { super(wallet, wallet.getName(), wallet.getWalletUtxos().entrySet().stream().map(entry -> new UtxoEntry(wallet, entry.getKey(), HashIndexEntry.Type.OUTPUT, entry.getValue())).collect(Collectors.toList())); calculateDuplicates(); - retrieveMixProgress(); + updateMixProgress(); } @Override @@ -38,13 +38,15 @@ public class WalletUtxosEntry extends Entry { } } - protected void retrieveMixProgress() { + public void updateMixProgress() { Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(getWallet()); if(whirlpool != null) { for(Entry entry : getChildren()) { UtxoEntry utxoEntry = (UtxoEntry)entry; MixProgress mixProgress = whirlpool.getMixProgress(utxoEntry.getHashIndex()); - utxoEntry.setMixProgress(mixProgress); + if(mixProgress != null || utxoEntry.getMixStatus() == null || (utxoEntry.getMixStatus().getMixFailReason() == null && utxoEntry.getMixStatus().getNextMixUtxo() == null)) { + utxoEntry.setMixProgress(mixProgress); + } } } } @@ -62,6 +64,6 @@ public class WalletUtxosEntry extends Entry { getChildren().removeAll(entriesRemoved); calculateDuplicates(); - retrieveMixProgress(); + updateMixProgress(); } } diff --git a/src/main/java/com/sparrowwallet/sparrow/whirlpool/Whirlpool.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/Whirlpool.java index 1c0b3af4..0f679aff 100644 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/Whirlpool.java +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/Whirlpool.java @@ -25,18 +25,15 @@ import com.sparrowwallet.drongo.ExtendedKey; import com.sparrowwallet.drongo.KeyPurpose; import com.sparrowwallet.drongo.Network; import com.sparrowwallet.drongo.Utils; -import com.sparrowwallet.drongo.protocol.ScriptType; -import com.sparrowwallet.drongo.protocol.Sha256Hash; -import com.sparrowwallet.drongo.protocol.Transaction; -import com.sparrowwallet.drongo.protocol.TransactionOutput; +import com.sparrowwallet.drongo.protocol.*; import com.sparrowwallet.drongo.wallet.*; import com.sparrowwallet.nightjar.http.JavaHttpClientService; import com.sparrowwallet.nightjar.stomp.JavaStompClientService; -import com.sparrowwallet.nightjar.tor.WhirlpoolTorClientService; import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.event.WhirlpoolMixEvent; import com.sparrowwallet.sparrow.event.WhirlpoolMixSuccessEvent; +import com.sparrowwallet.sparrow.io.Config; import com.sparrowwallet.sparrow.wallet.UtxoEntry; import com.sparrowwallet.sparrow.whirlpool.dataPersister.SparrowDataPersister; import com.sparrowwallet.sparrow.whirlpool.dataSource.SparrowDataSource; @@ -49,6 +46,7 @@ import javafx.beans.property.SimpleBooleanProperty; import javafx.concurrent.ScheduledService; import javafx.concurrent.Service; import javafx.concurrent.Task; +import javafx.util.Duration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -75,6 +73,7 @@ public class Whirlpool { private HD_Wallet hdWallet; private String walletId; private String mixToWalletId; + private boolean resyncMixesDone; private StartupService startupService; @@ -95,10 +94,14 @@ public class Whirlpool { this.tx0Service = new Tx0Service(config); WhirlpoolEventService.getInstance().register(this); + + StatusReporterService statusReporterService = new StatusReporterService(this); + statusReporterService.setPeriod(Duration.minutes(1)); + statusReporterService.start(); } private WhirlpoolWalletConfig computeWhirlpoolWalletConfig(HostAndPort torProxy) { - DataPersisterFactory dataPersisterFactory = (whirlpoolWallet, bip44w) -> new SparrowDataPersister(whirlpoolWallet); + DataPersisterFactory dataPersisterFactory = (whirlpoolWallet, bip44w) -> new SparrowDataPersister(whirlpoolWallet, config.getPersistDelaySeconds()); DataSourceFactory dataSourceFactory = (whirlpoolWallet, bip44w, dataPersister) -> new SparrowDataSource(whirlpoolWallet, bip44w, dataPersister); boolean onion = (torProxy != null); @@ -108,9 +111,20 @@ public class Whirlpool { WhirlpoolWalletConfig whirlpoolWalletConfig = new WhirlpoolWalletConfig(dataSourceFactory, httpClientService, stompClientService, torClientService, serverApi, whirlpoolServer.getParams(), false); whirlpoolWalletConfig.setDataPersisterFactory(dataPersisterFactory); whirlpoolWalletConfig.setPartner("SPARROW"); + whirlpoolWalletConfig.setIndexRangePostmix(Config.get().getPostmixIndexRange()); return whirlpoolWalletConfig; } + public Pool getPool(String poolId) { + try { + return getPools(null).stream().filter(pool -> pool.getPoolId().equals(poolId)).findFirst().orElse(null); + } catch(Exception e) { + log.error("Error retrieving pools", e); + } + + return null; + } + public Collection getPools(Long totalUtxoValue) throws Exception { this.poolSupplier.load(); if(totalUtxoValue == null) { @@ -233,18 +247,73 @@ public class Whirlpool { WhirlpoolUtxo whirlpoolUtxo = whirlpoolWalletService.whirlpoolWallet().getUtxoSupplier().findUtxo(utxo.getHashAsString(), (int)utxo.getIndex()); if(whirlpoolUtxo != null && whirlpoolUtxo.getUtxoState() != null) { - return whirlpoolUtxo.getUtxoState().getMixProgress(); + MixProgress mixProgress = whirlpoolUtxo.getUtxoState().getMixProgress(); + if(mixProgress != null && !isMixing(utxo)) { + log.debug("Utxo " + utxo + " mix state is " + whirlpoolUtxo.getUtxoState() + " but utxo is not mixing"); + return null; + } + + return mixProgress; } return null; } + private boolean isMixing(BlockTransactionHashIndex utxo) { + if(whirlpoolWalletService.whirlpoolWallet() == null || !whirlpoolWalletService.whirlpoolWallet().isStarted()) { + return false; + } + + return whirlpoolWalletService.whirlpoolWallet().getMixingState().getUtxosMixing().stream().map(WhirlpoolUtxo::getUtxo).anyMatch(uo -> uo.tx_hash.equals(utxo.getHashAsString()) && uo.tx_output_n == (int)utxo.getIndex()); + } + public void refreshUtxos() { if(whirlpoolWalletService.whirlpoolWallet() != null) { whirlpoolWalletService.whirlpoolWallet().refreshUtxos(); } } + private void resyncMixesDone(Whirlpool whirlpool, Wallet postmixWallet) { + Set receiveUtxos = postmixWallet.getWalletUtxos().entrySet().stream() + .filter(entry -> entry.getValue().getKeyPurpose() == KeyPurpose.RECEIVE).map(Map.Entry::getKey).collect(Collectors.toSet()); + for(BlockTransactionHashIndex utxo : receiveUtxos) { + int mixesDone = recountMixesDone(postmixWallet, utxo); + whirlpool.setMixesDone(utxo, mixesDone); + } + } + + public int recountMixesDone(Wallet postmixWallet, BlockTransactionHashIndex postmixUtxo) { + int mixesDone = 0; + Set walletTxos = postmixWallet.getWalletTxos().entrySet().stream() + .filter(entry -> entry.getValue().getKeyPurpose() == KeyPurpose.RECEIVE).map(Map.Entry::getKey).collect(Collectors.toSet()); + BlockTransaction blkTx = postmixWallet.getTransactions().get(postmixUtxo.getHash()); + + while(blkTx != null) { + mixesDone++; + List inputs = blkTx.getTransaction().getInputs(); + blkTx = null; + for(TransactionInput txInput : inputs) { + BlockTransaction inputTx = postmixWallet.getTransactions().get(txInput.getOutpoint().getHash()); + if(inputTx != null && walletTxos.stream().anyMatch(txo -> txo.getHash().equals(inputTx.getHash()) && txo.getIndex() == txInput.getOutpoint().getIndex()) && inputTx.getTransaction() != null) { + blkTx = inputTx; + } + } + } + + return mixesDone; + } + + public void setMixesDone(BlockTransactionHashIndex utxo, int mixesDone) { + if(whirlpoolWalletService.whirlpoolWallet() == null) { + return; + } + + WhirlpoolUtxo whirlpoolUtxo = whirlpoolWalletService.whirlpoolWallet().getUtxoSupplier().findUtxo(utxo.getHashAsString(), (int)utxo.getIndex()); + if(whirlpoolUtxo != null) { + whirlpoolUtxo.setMixsDone(mixesDone); + } + } + public boolean hasWallet() { return hdWallet != null; } @@ -385,10 +454,6 @@ public class Whirlpool { config.setScode(scode); } - public Tx0FeeTarget getTx0FeeTarget() { - return tx0FeeTarget; - } - public void setTx0FeeTarget(Tx0FeeTarget tx0FeeTarget) { this.tx0FeeTarget = tx0FeeTarget; } @@ -401,6 +466,10 @@ public class Whirlpool { return mixToWalletId; } + public void setResyncMixesDone(boolean resyncMixesDone) { + this.resyncMixesDone = resyncMixesDone; + } + public void setMixToWallet(String mixToWalletId, Integer minMixes) { if(mixToWalletId == null) { config.setExternalDestination(null); @@ -488,6 +557,15 @@ public class Whirlpool { if(e.getWhirlpoolWallet() == whirlpoolWalletService.whirlpoolWallet()) { log.info("Mixing to " + e.getWhirlpoolWallet().getConfig().getExternalDestination()); mixingProperty.set(true); + + if(resyncMixesDone) { + Wallet wallet = AppServices.get().getWallet(walletId); + if(wallet != null) { + Wallet postmixWallet = getStandardAccountWallet(WhirlpoolAccount.POSTMIX, wallet); + resyncMixesDone(this, postmixWallet); + resyncMixesDone = false; + } + } } } @@ -632,6 +710,54 @@ public class Whirlpool { } } + public static class RegisteredInputsService extends Service { + private final Whirlpool whirlpool; + private final String poolId; + + public RegisteredInputsService(Whirlpool whirlpool, String poolId) { + this.whirlpool = whirlpool; + this.poolId = poolId; + } + + @Override + protected Task createTask() { + return new Task<>() { + protected Integer call() { + Pool pool = whirlpool.getPool(poolId); + if(pool != null) { + return pool.getNbRegistered(); + } + + return null; + } + }; + } + } + + private static class StatusReporterService extends ScheduledService { + private final Whirlpool whirlpool; + + public StatusReporterService(Whirlpool whirlpool) { + this.whirlpool = whirlpool; + } + + @Override + protected Task createTask() { + return new Task<>() { + protected Boolean call() throws Exception { + if(whirlpool.mixingProperty().get()) { + WhirlpoolWallet whirlpoolWallet = whirlpool.getWhirlpoolWallet(); + log.debug(whirlpool.walletId + ": " + whirlpoolWallet.getMixingState()); + } else { + log.debug(whirlpool.walletId + ": Not mixing"); + } + + return true; + } + }; + } + } + public static class WalletUtxo { public final Wallet wallet; public final BlockTransactionHashIndex utxo; diff --git a/src/main/java/com/sparrowwallet/sparrow/whirlpool/WhirlpoolServices.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/WhirlpoolServices.java index 8f8533c0..b42e77dc 100644 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/WhirlpoolServices.java +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/WhirlpoolServices.java @@ -156,6 +156,7 @@ public class WhirlpoolServices { Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(walletId); whirlpool.setScode(decryptedWallet.getMasterMixConfig().getScode()); whirlpool.setHDWallet(walletId, decryptedWallet); + whirlpool.setResyncMixesDone(true); for(StandardAccount whirlpoolAccount : StandardAccount.WHIRLPOOL_ACCOUNTS) { if(decryptedWallet.getChildWallet(whirlpoolAccount) == null) { 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 fbe1e4c2..7f6d942e 100644 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataPersister/SparrowDataPersister.java +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataPersister/SparrowDataPersister.java @@ -1,5 +1,6 @@ package com.sparrowwallet.sparrow.whirlpool.dataPersister; +import com.samourai.wallet.util.AbstractOrchestrator; import com.samourai.whirlpool.client.wallet.WhirlpoolWallet; import com.samourai.whirlpool.client.wallet.WhirlpoolWalletConfig; import com.samourai.whirlpool.client.wallet.data.dataPersister.DataPersister; @@ -7,24 +8,49 @@ import com.samourai.whirlpool.client.wallet.data.utxoConfig.UtxoConfigPersistedS import com.samourai.whirlpool.client.wallet.data.utxoConfig.UtxoConfigSupplier; import com.samourai.whirlpool.client.wallet.data.walletState.WalletStateSupplier; import com.sparrowwallet.sparrow.whirlpool.dataSource.SparrowWalletStateSupplier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class SparrowDataPersister implements DataPersister { + private static final Logger log = LoggerFactory.getLogger(SparrowDataPersister.class); + private final WalletStateSupplier walletStateSupplier; private final UtxoConfigSupplier utxoConfigSupplier; - public SparrowDataPersister(WhirlpoolWallet whirlpoolWallet) throws Exception { + private AbstractOrchestrator persistOrchestrator; + private final int persistDelaySeconds; + + public SparrowDataPersister(WhirlpoolWallet whirlpoolWallet, int persistDelaySeconds) throws Exception { WhirlpoolWalletConfig config = whirlpoolWallet.getConfig(); String walletIdentifier = whirlpoolWallet.getWalletIdentifier(); this.walletStateSupplier = new SparrowWalletStateSupplier(walletIdentifier, config); this.utxoConfigSupplier = new UtxoConfigPersistedSupplier(new SparrowUtxoConfigPersister(walletIdentifier)); + this.persistDelaySeconds = persistDelaySeconds; } @Override public void open() throws Exception { + startPersistOrchestrator(); + } + + protected void startPersistOrchestrator() { + persistOrchestrator = new AbstractOrchestrator(persistDelaySeconds * 1000) { + @Override + protected void runOrchestrator() { + try { + persist(false); + } catch (Exception e) { + log.error("Error persisting Whirlpool data", e); + } + } + }; + + persistOrchestrator.start(true); } @Override public void close() throws Exception { + persistOrchestrator.stop(); } @Override diff --git a/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataPersister/SparrowUtxoConfigPersister.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataPersister/SparrowUtxoConfigPersister.java index 7453ce87..33784b6a 100644 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataPersister/SparrowUtxoConfigPersister.java +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataPersister/SparrowUtxoConfigPersister.java @@ -62,7 +62,9 @@ public class SparrowUtxoConfigPersister extends UtxoConfigPersister { wallet.getUtxoMixes().putAll(changedUtxoMixes); wallet.getUtxoMixes().keySet().removeAll(removedUtxoMixes.keySet()); - EventManager.get().post(new WalletUtxoMixesChangedEvent(wallet, changedUtxoMixes, removedUtxoMixes)); + if(!changedUtxoMixes.isEmpty() || !removedUtxoMixes.isEmpty()) { + EventManager.get().post(new WalletUtxoMixesChangedEvent(wallet, changedUtxoMixes, removedUtxoMixes)); + } } private Wallet getWallet() {