Skip to content

Commit

Permalink
feat(coinjoin): add mixing progress method to CoinJoinExtension (#246)
Browse files Browse the repository at this point in the history
* feat(coinjoin): add mixing progress method to CoinJoinExtension

* fix(coinjoin): reduce logging
  • Loading branch information
HashEngineering authored Mar 12, 2024
1 parent f6cd23d commit 9c66d59
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,20 @@
import org.bitcoinj.core.Context;
import org.bitcoinj.core.MasternodeSync;
import org.bitcoinj.core.Peer;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Utils;
import org.bitcoinj.evolution.Masternode;
import org.bitcoinj.evolution.SimplifiedMasternodeList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.function.Predicate;

public class CoinJoinClientQueueManager extends CoinJoinBaseManager {
private final Context context;
private final Logger log = LoggerFactory.getLogger(CoinJoinClientManager.class);
private final HashMap<Sha256Hash, Long> spammingMasternodes = new HashMap();

public CoinJoinClientQueueManager(Context context) {
super();
Expand All @@ -45,7 +49,10 @@ public void processDSQueue(Peer from, CoinJoinQueue dsq, boolean enable_bip61) {
}
if (q.isReady() == dsq.isReady() && q.getProTxHash().equals(dsq.getProTxHash())) {
// no way the same mn can send another dsq with the same readiness this soon
log.debug("coinjoin: DSQUEUE -- Peer {} is sending WAY too many dsq messages for a masternode {}", from.getAddress().getAddr(), dsq.getProTxHash());
if (!spammingMasternodes.containsKey(dsq.getProTxHash())) {
spammingMasternodes.put(dsq.getProTxHash(), Utils.currentTimeMillis());
log.info("coinjoin: DSQUEUE -- Peer {} is sending WAY too many dsq messages for a masternode {}", from.getAddress().getAddr(), dsq.getProTxHash());
}
return;
}
}
Expand Down Expand Up @@ -79,7 +86,10 @@ public void processDSQueue(Peer from, CoinJoinQueue dsq, boolean enable_bip61) {
log.info("coinjoin: DSQUEUE -- lastDsq: {} dsqThreshold: {} dsqCount: {}", nLastDsq, nDsqThreshold, context.masternodeMetaDataManager.getDsqCount());
// don't allow a few nodes to dominate the queuing process
if (nLastDsq != 0 && nDsqThreshold > context.masternodeMetaDataManager.getDsqCount()) {
log.info("coinjoin: DSQUEUE -- Masternode {} is sending too many dsq messages", dmn.getProTxHash());
if (!spammingMasternodes.containsKey(dsq.getProTxHash())) {
spammingMasternodes.put(dsq.getProTxHash(), Utils.currentTimeMillis());
log.info("coinjoin: DSQUEUE -- Masternode {} is sending too many dsq messages", dmn.getProTxHash());
}
return;
}

Expand Down Expand Up @@ -127,5 +137,7 @@ public void doMaintenance() {
return;

checkQueue();

spammingMasternodes.entrySet().removeIf(entry -> entry.getValue() + CoinJoinConstants.COINJOIN_QUEUE_TIMEOUT * 1000 < Utils.currentTimeMillis());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1356,16 +1356,16 @@ public boolean doAutomaticDenominating(boolean fDryRun) {
log.info("coinjoin: wallet stats:\n{}", bal);

log.info("coinjoin: current stats:\n" +
" nValueMin: {}\n" +
" min: {}\n" +
" myTrusted: {}\n" +
" nBalanceAnonymizable: {}\n" +
" nBalanceAnonymized: {}\n" +
" balanceAnonymizable: {}\n" +
" balanceAnonymized: {}\n" +
" balanceNeedsAnonymized: {}\n" +
" nBalanceAnonimizableNonDenom: {}\n" +
" nBalanceDenominatedConf: {}\n" +
" nBalanceDenominatedUnconf: {}\n" +
" nBalanceDenominated: {}\n" +
" nBalanceToDenominate: {}\n",
" balanceAnonimizableNonDenom: {}\n" +
" balanceDenominatedConf: {}\n" +
" balanceDenominatedUnconf: {}\n" +
" balanceDenominated: {}\n" +
" balanceToDenominate: {}\n",
nValueMin.toFriendlyString(),
bal.getMyTrusted().toFriendlyString(),
nBalanceAnonymizable.toFriendlyString(),
Expand Down
55 changes: 52 additions & 3 deletions core/src/main/java/org/bitcoinj/wallet/CoinJoinExtension.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,13 @@
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Stream;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static java.lang.Math.max;
import static org.dashj.bls.Utils.HexUtils.HEX;

/**
Expand Down Expand Up @@ -352,12 +354,12 @@ public DeterministicKey getUnusedKey() {
try {
if (unusedKeys.isEmpty()) {
log.info("obtaining fresh key");
log.info("keyUsage map has unused keys: {}", keyUsage.values().stream().noneMatch(used -> used));
log.info("keyUsage map has unused keys: {}", keyUsage.values().stream().noneMatch(used -> used), getUnusedKeyCount());
return (DeterministicKey) freshReceiveKey();
} else {
DeterministicKey key = unusedKeys.values().stream().findFirst().get();
log.info("reusing key: {} / {}", HEX.encode(key.getPubKeyHash()), key);
log.info("keyUsage map says this key is used: {}", keyUsage.get(key));
log.info("keyUsage map says this key is used: {}, unused key count: {}", keyUsage.get(key), getUnusedKeyCount());

// remove the key
unusedKeys.remove(KeyId.fromBytes(key.getPubKeyHash()));
Expand Down Expand Up @@ -509,8 +511,28 @@ public String getUnusedKeyReport() {
return Integer.compare(size1, size2);
});

// add the sorted list of unused keys
builder.append("Unused Key List: ");
sortedPaths.forEach(path -> builder.append(" ").append(path).append("\n"));
AtomicInteger largestGap = new AtomicInteger(0);
AtomicInteger currentGap = new AtomicInteger(0);
AtomicInteger lastIndex = new AtomicInteger(-1);
sortedPaths.forEach(path -> {
builder.append(" ").append(path).append("\n");
int index = path.get(path.size() -1).i();
if (lastIndex.get() != -1) {
if (lastIndex.get() + 1 == index) {
currentGap.getAndIncrement();
} else {
largestGap.set(max(largestGap.get(), currentGap.getAndSet(0)));
}
} else {
currentGap.set(0);
}
lastIndex.set(index);
});

// add gap information
builder.append("Largest Gap: ").append(largestGap.get()).append("\n");
return builder.toString();

} finally {
Expand Down Expand Up @@ -561,4 +583,31 @@ public String getKeyUsageReport() {
unusedKeysLock.unlock();
}
}

public double getMixingProgress() {
double requiredRounds = rounds + 0.875; // 1 x 50% + 1 x 50%^2 + 1 x 50%^3
AtomicInteger totalInputs = new AtomicInteger();
AtomicInteger totalRounds = new AtomicInteger();
getOutputs().forEach((denom, outputs) -> {
outputs.forEach(output -> {
// do not count mixing collateral for fees
if (denom != -1) {
// getOutputs has a bug where non-denominated items are marked as denominated
TransactionOutPoint outPoint = new TransactionOutPoint(output.getParams(), output.getIndex(), output.getParentTransactionHash());
int rounds = ((WalletEx) wallet).getRealOutpointCoinJoinRounds(outPoint);
if (rounds >= 0) {
totalInputs.addAndGet(1);
totalRounds.addAndGet(rounds);
}
}
});
});
double progress = totalRounds.get() / (requiredRounds * totalInputs.get());
log.info("getMixingProgress: {} = {} / ({} * {})", progress, totalRounds.get(), requiredRounds, totalInputs.get());
return Math.max(0.0, Math.min(progress, 1.0));
}

public int getUnusedKeyCount() {
return unusedKeys.size();
}
}

0 comments on commit 9c66d59

Please sign in to comment.