add dust attack warning to utxos tab where small value txes are received on used addresses

This commit is contained in:
Craig Raw 2022-05-18 08:38:47 +02:00
parent 674498052f
commit c0ca74ce6a
5 changed files with 55 additions and 6 deletions

View file

@ -37,11 +37,13 @@ public class AddressCell extends TreeTableCell<Entry, UtxoEntry.AddressStatus> {
setContextMenu(new EntryCell.AddressContextMenu(address, utxoEntry.getOutputDescriptor(), new NodeEntry(utxoEntry.getWallet(), utxoEntry.getNode()))); setContextMenu(new EntryCell.AddressContextMenu(address, utxoEntry.getOutputDescriptor(), new NodeEntry(utxoEntry.getWallet(), utxoEntry.getNode())));
Tooltip tooltip = new Tooltip(); Tooltip tooltip = new Tooltip();
tooltip.setShowDelay(Duration.millis(250)); tooltip.setShowDelay(Duration.millis(250));
tooltip.setText(getTooltipText(utxoEntry, addressStatus.isDuplicate())); tooltip.setText(getTooltipText(utxoEntry, addressStatus.isDuplicate(), addressStatus.isDustAttack()));
setTooltip(tooltip); setTooltip(tooltip);
if(addressStatus.isDuplicate()) { if(addressStatus.isDuplicate()) {
setGraphic(getDuplicateGlyph()); setGraphic(getDuplicateGlyph());
} else if(addressStatus.isDustAttack()) {
setGraphic(getDustAttackGlyph());
} else { } else {
setGraphic(null); setGraphic(null);
} }
@ -49,9 +51,9 @@ public class AddressCell extends TreeTableCell<Entry, UtxoEntry.AddressStatus> {
} }
} }
private String getTooltipText(UtxoEntry utxoEntry, boolean duplicate) { private String getTooltipText(UtxoEntry utxoEntry, boolean duplicate, boolean dustAttack) {
return (utxoEntry.getNode().getWallet().isNested() ? utxoEntry.getNode().getWallet().getDisplayName() + " " : "" ) + return (utxoEntry.getNode().getWallet().isNested() ? utxoEntry.getNode().getWallet().getDisplayName() + " " : "" ) +
utxoEntry.getNode().toString() + (duplicate ? " (Duplicate address)" : ""); utxoEntry.getNode().toString() + (duplicate ? " (Duplicate address)" : (dustAttack ? " (Possible dust attack)" : ""));
} }
public static Glyph getDuplicateGlyph() { public static Glyph getDuplicateGlyph() {
@ -60,4 +62,11 @@ public class AddressCell extends TreeTableCell<Entry, UtxoEntry.AddressStatus> {
duplicateGlyph.setFontSize(12); duplicateGlyph.setFontSize(12);
return duplicateGlyph; return duplicateGlyph;
} }
public static Glyph getDustAttackGlyph() {
Glyph dustAttackGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.EXCLAMATION_TRIANGLE);
dustAttackGlyph.getStyleClass().add("dust-attack-warning");
dustAttackGlyph.setFontSize(12);
return dustAttackGlyph;
}
} }

View file

@ -18,6 +18,7 @@ import java.util.stream.Collectors;
import static com.sparrowwallet.sparrow.AppServices.ENUMERATE_HW_PERIOD_SECS; import static com.sparrowwallet.sparrow.AppServices.ENUMERATE_HW_PERIOD_SECS;
import static com.sparrowwallet.sparrow.net.PagedBatchRequestBuilder.DEFAULT_PAGE_SIZE; import static com.sparrowwallet.sparrow.net.PagedBatchRequestBuilder.DEFAULT_PAGE_SIZE;
import static com.sparrowwallet.sparrow.net.TcpTransport.DEFAULT_MAX_TIMEOUT; import static com.sparrowwallet.sparrow.net.TcpTransport.DEFAULT_MAX_TIMEOUT;
import static com.sparrowwallet.sparrow.wallet.WalletUtxosEntry.DUST_ATTACK_THRESHOLD_SATS;
public class Config { public class Config {
private static final Logger log = LoggerFactory.getLogger(Config.class); private static final Logger log = LoggerFactory.getLogger(Config.class);
@ -45,6 +46,7 @@ public class Config {
private boolean preventSleep = false; private boolean preventSleep = false;
private List<File> recentWalletFiles; private List<File> recentWalletFiles;
private Integer keyDerivationPeriod; private Integer keyDerivationPeriod;
private long dustAttackThreshold = DUST_ATTACK_THRESHOLD_SATS;
private File hwi; private File hwi;
private int enumerateHwPeriod = ENUMERATE_HW_PERIOD_SECS; private int enumerateHwPeriod = ENUMERATE_HW_PERIOD_SECS;
private Boolean hdCapture; private Boolean hdCapture;
@ -302,6 +304,10 @@ public class Config {
flush(); flush();
} }
public long getDustAttackThreshold() {
return dustAttackThreshold;
}
public File getHwi() { public File getHwi() {
return hwi; return hwi;
} }

View file

@ -69,7 +69,13 @@ public class UtxoEntry extends HashIndexEntry {
private ObjectProperty<AddressStatus> addressStatusProperty; private ObjectProperty<AddressStatus> addressStatusProperty;
public final void setDuplicateAddress(boolean value) { public final void setDuplicateAddress(boolean value) {
addressStatusProperty().set(new AddressStatus(value)); AddressStatus addressStatus = addressStatusProperty().get();
addressStatusProperty().set(new AddressStatus(value, addressStatus.dustAttack));
}
public final void setDustAttack(boolean value) {
AddressStatus addressStatus = addressStatusProperty().get();
addressStatusProperty().set(new AddressStatus(addressStatus.duplicate, value));
} }
public final boolean isDuplicateAddress() { public final boolean isDuplicateAddress() {
@ -78,7 +84,7 @@ public class UtxoEntry extends HashIndexEntry {
public final ObjectProperty<AddressStatus> addressStatusProperty() { public final ObjectProperty<AddressStatus> addressStatusProperty() {
if(addressStatusProperty == null) { if(addressStatusProperty == null) {
addressStatusProperty = new SimpleObjectProperty<>(UtxoEntry.this, "addressStatus", new AddressStatus(false)); addressStatusProperty = new SimpleObjectProperty<>(UtxoEntry.this, "addressStatus", new AddressStatus(false, false));
} }
return addressStatusProperty; return addressStatusProperty;
@ -86,9 +92,11 @@ public class UtxoEntry extends HashIndexEntry {
public class AddressStatus { public class AddressStatus {
private final boolean duplicate; private final boolean duplicate;
private final boolean dustAttack;
public AddressStatus(boolean duplicate) { public AddressStatus(boolean duplicate, boolean dustAttack) {
this.duplicate = duplicate; this.duplicate = duplicate;
this.dustAttack = dustAttack;
} }
public UtxoEntry getUtxoEntry() { public UtxoEntry getUtxoEntry() {
@ -102,6 +110,10 @@ public class UtxoEntry extends HashIndexEntry {
public boolean isDuplicate() { public boolean isDuplicate() {
return duplicate; return duplicate;
} }
public boolean isDustAttack() {
return dustAttack;
}
} }
/** /**

View file

@ -2,16 +2,21 @@ package com.sparrowwallet.sparrow.wallet;
import com.samourai.whirlpool.client.wallet.beans.MixProgress; import com.samourai.whirlpool.client.wallet.beans.MixProgress;
import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.drongo.wallet.WalletNode;
import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.io.Config;
import com.sparrowwallet.sparrow.whirlpool.Whirlpool; import com.sparrowwallet.sparrow.whirlpool.Whirlpool;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class WalletUtxosEntry extends Entry { public class WalletUtxosEntry extends Entry {
public static final int DUST_ATTACK_THRESHOLD_SATS = 1000;
public WalletUtxosEntry(Wallet wallet) { public WalletUtxosEntry(Wallet wallet) {
super(wallet, wallet.getName(), wallet.getWalletUtxos().entrySet().stream().map(entry -> new UtxoEntry(entry.getValue().getWallet(), entry.getKey(), HashIndexEntry.Type.OUTPUT, entry.getValue())).collect(Collectors.toList())); super(wallet, wallet.getName(), wallet.getWalletUtxos().entrySet().stream().map(entry -> new UtxoEntry(entry.getValue().getWallet(), entry.getKey(), HashIndexEntry.Type.OUTPUT, entry.getValue())).collect(Collectors.toList()));
calculateDuplicates(); calculateDuplicates();
calculateDust();
updateMixProgress(); updateMixProgress();
} }
@ -48,6 +53,18 @@ public class WalletUtxosEntry extends Entry {
} }
} }
protected void calculateDust() {
long dustAttackThreshold = Config.get().getDustAttackThreshold();
Set<WalletNode> duplicateNodes = getWallet().getWalletTxos().values().stream()
.collect(Collectors.groupingBy(e -> e, Collectors.counting()))
.entrySet().stream().filter(e -> e.getValue() > 1).map(Map.Entry::getKey).collect(Collectors.toSet());
for(Entry entry : getChildren()) {
UtxoEntry utxoEntry = (UtxoEntry) entry;
utxoEntry.setDustAttack(utxoEntry.getValue() <= dustAttackThreshold && duplicateNodes.contains(utxoEntry.getNode()));
}
}
public void updateMixProgress() { public void updateMixProgress() {
Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(getWallet()); Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(getWallet());
if(whirlpool != null) { if(whirlpool != null) {
@ -74,6 +91,7 @@ public class WalletUtxosEntry extends Entry {
getChildren().removeAll(entriesRemoved); getChildren().removeAll(entriesRemoved);
calculateDuplicates(); calculateDuplicates();
calculateDust();
updateMixProgress(); updateMixProgress();
} }

View file

@ -123,6 +123,10 @@
-fx-text-fill: rgb(202, 18, 67); -fx-text-fill: rgb(202, 18, 67);
} }
.dust-attack-warning {
-fx-text-fill: rgb(238, 210, 2);
}
.unused-check { .unused-check {
-fx-text-fill: #50a14f; -fx-text-fill: #50a14f;
} }