diff --git a/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagram.java b/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagram.java index 67641ff0..b524eab2 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagram.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagram.java @@ -999,7 +999,7 @@ public class TransactionDiagram extends GridPane { } private boolean isDuplicateAddress(Payment payment) { - return walletTx.getPayments().stream().filter(p -> payment != p).anyMatch(p -> payment.getAddress().equals(p.getAddress())); + return walletTx.getPayments().stream().filter(p -> payment != p).anyMatch(p -> payment.getAddress() != null && payment.getAddress().equals(p.getAddress())); } public static Glyph getExcludeGlyph() { diff --git a/src/main/java/com/sparrowwallet/sparrow/event/WhirlpoolIndexHighFrequencyEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/WhirlpoolIndexHighFrequencyEvent.java new file mode 100644 index 00000000..a91065ca --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/event/WhirlpoolIndexHighFrequencyEvent.java @@ -0,0 +1,15 @@ +package com.sparrowwallet.sparrow.event; + +import com.sparrowwallet.drongo.wallet.Wallet; + +public class WhirlpoolIndexHighFrequencyEvent { + private final Wallet wallet; + + public WhirlpoolIndexHighFrequencyEvent(Wallet wallet) { + this.wallet = wallet; + } + + public Wallet getWallet() { + return wallet; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/whirlpool/Whirlpool.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/Whirlpool.java index 57510685..9f0984cb 100644 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/Whirlpool.java +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/Whirlpool.java @@ -45,6 +45,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 +76,7 @@ public class Whirlpool { private boolean resyncMixesDone; private StartupService startupService; + private Duration startupServiceDelay; private final BooleanProperty startingProperty = new SimpleBooleanProperty(false); private final BooleanProperty stoppingProperty = new SimpleBooleanProperty(false); @@ -563,6 +565,14 @@ public class Whirlpool { return stoppingProperty; } + public Duration getStartupServiceDelay() { + return startupServiceDelay; + } + + public void setStartupServiceDelay(Duration startupServiceDelay) { + this.startupServiceDelay = startupServiceDelay; + } + @Subscribe public void onMixSuccess(MixSuccessEvent e) { WalletUtxo walletUtxo = getUtxo(e.getWhirlpoolUtxo()); diff --git a/src/main/java/com/sparrowwallet/sparrow/whirlpool/WhirlpoolServices.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/WhirlpoolServices.java index 9ddef872..5ee27c7f 100644 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/WhirlpoolServices.java +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/WhirlpoolServices.java @@ -15,6 +15,7 @@ import com.sparrowwallet.sparrow.WalletTabData; import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.io.Storage; import com.sparrowwallet.sparrow.soroban.Soroban; +import javafx.application.Platform; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCombination; @@ -105,6 +106,11 @@ public class WhirlpoolServices { } Whirlpool.StartupService startupService = whirlpool.createStartupService(); + if(whirlpool.getStartupServiceDelay() != null) { + startupService.setDelay(whirlpool.getStartupServiceDelay()); + whirlpool.setStartupServiceDelay(null); + } + startupService.setPeriod(Duration.minutes(2)); startupService.setOnSucceeded(workerStateEvent -> { startupService.cancel(); @@ -281,4 +287,17 @@ public class WhirlpoolServices { whirlpool.refreshUtxos(); } } + + @Subscribe + public void whirlpoolIndexHighFrequency(WhirlpoolIndexHighFrequencyEvent event) { + Whirlpool whirlpool = getWhirlpool(event.getWallet()); + if(whirlpool != null && whirlpool.isStarted() && !whirlpool.isStopping()) { + log.warn("Rapidly increasing address index detected, temporarily disconnecting " + event.getWallet().getMasterName() + " from Whirlpool"); + Platform.runLater(() -> { + EventManager.get().post(new StatusEvent("Error communicating with Whirlpool, will retry soon...")); + whirlpool.setStartupServiceDelay(Duration.minutes(5)); + stopWhirlpool(whirlpool, false); + }); + } + } } 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 e7bc9ead..f66b4f8b 100644 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowIndexHandler.java +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowIndexHandler.java @@ -8,12 +8,17 @@ import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.event.WalletGapLimitChangedEvent; import com.sparrowwallet.sparrow.event.WalletMixConfigChangedEvent; +import com.sparrowwallet.sparrow.event.WhirlpoolIndexHighFrequencyEvent; public class SparrowIndexHandler extends AbstractIndexHandler { private final Wallet wallet; private final WalletNode walletNode; private final int defaultValue; + private static final long PERIOD = 1000 * 60 * 10L; //Period of 10 minutes + private long periodStart; + private int periodCount; + public SparrowIndexHandler(Wallet wallet, WalletNode walletNode) { this(wallet, walletNode, 0); } @@ -77,6 +82,20 @@ public class SparrowIndexHandler extends AbstractIndexHandler { if(index > highestUsedIndex + existingGapLimit) { wallet.setGapLimit(Math.max(wallet.getGapLimit(), index - highestUsedIndex)); EventManager.get().post(new WalletGapLimitChangedEvent(getWalletId(), wallet, existingGapLimit)); + checkFrequency(); + } + } + + private void checkFrequency() { + if(periodStart > 0 && System.currentTimeMillis() - periodStart < PERIOD) { + periodCount++; + } else { + periodStart = System.currentTimeMillis(); + periodCount = 0; + } + + if(periodCount >= Wallet.DEFAULT_LOOKAHEAD) { + EventManager.get().post(new WhirlpoolIndexHighFrequencyEvent(wallet)); } }