Skip to content

Commit

Permalink
Feat(coinjoin): improve coinjoin monitor (#224)
Browse files Browse the repository at this point in the history
* feat(coinjoin): Improve CoinJoinMonitor

* track blocks
* silence logging
* improve stats
* log times

* feat(coinjoin): add block information to CoinJoinReporter

* feat(examples): add mixing estimates to CoinJoinMonitor

* tests: fix CoinJoinSessionTest
  • Loading branch information
HashEngineering authored Aug 14, 2024
1 parent 8f8a898 commit b5abdeb
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.VerificationException;
import org.bitcoinj.core.listeners.NewBestBlockListener;
import org.bitcoinj.core.StoredBlock;
import org.bitcoinj.core.VerificationException;
import org.bitcoinj.core.listeners.NewBestBlockListener;
import org.bitcoinj.wallet.Wallet;
import org.bitcoinj.wallet.WalletEx;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.bitcoinj.core.StoredBlock;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.core.StoredBlock;
import org.bitcoinj.core.Utils;
import org.bitcoinj.core.VerificationException;
import org.bitcoinj.wallet.Wallet;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ public void setUp() throws Exception {
coinbase,
Collections.singletonList(entry),
Collections.emptyList(),
SimplifiedMasternodeListDiff.LEGACY_BLS_VERSION
SimplifiedMasternodeListDiff.CURRENT_VERSION
);
wallet.getContext().masternodeListManager.processMasternodeListDiff(null, diff, true);

Expand Down
57 changes: 27 additions & 30 deletions examples/src/main/java/org/bitcoinj/examples/CoinJoinMonitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import org.bitcoinj.core.PeerGroup;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.StoredBlock;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.Utils;
import org.bitcoinj.core.VerificationException;
import org.bitcoinj.core.listeners.NewBestBlockListener;
Expand All @@ -46,14 +45,13 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Scanner;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
Expand Down Expand Up @@ -131,27 +129,27 @@ private static void calculateDSTX(long timeInterval) {

System.out.println("Denominations Mixed ----------------------------");
for (Map.Entry<Coin, DenomInfo> entry : denoms.entrySet()) {
System.out.println(entry.getKey().toFriendlyString() + ": " + entry.getValue().count +
" / " + entry.getValue().total.toFriendlyString() + "; " + entry.getValue().getUsage() + "%" +
"; " + entry.getValue().getRate(hours) + " DASH/hr");
System.out.printf("%10s: %d / %s; %.1f %.8f DASH/hr\n", entry.getKey().toFriendlyString(), entry.getValue().count,
entry.getValue().total.toFriendlyString(), entry.getValue().getUsage(),
entry.getValue().getRate(hours));
}

System.out.println("Network Utilization ----------------------------");
System.out.println("Masternodes: " + Context.get().masternodeListManager.getMasternodeList().countEnabled());
System.out.println("Hours: " + hours);
System.out.println("Sessions/hour: " + completedSessions.size() / hours);
System.out.printf("Hours: %.1f\n", hours);
System.out.printf("Sessions/hour: %.1f\n", (double) completedSessions.size() / hours);


System.out.println("Session Times");
for (SessionInfo sessionInfo : completedSessions) {
System.out.println(CoinJoin.denominationToAmount(sessionInfo.dsq.getDenomination()).toFriendlyString() +
":" + sessionInfo.watch.elapsed(TimeUnit.SECONDS) + "s " + sessionInfo.dstx.getTx().getInputs().size() + "/10");
}
//System.out.println("Session Times");
//for (SessionInfo sessionInfo : completedSessions) {
// System.out.println(CoinJoin.denominationToAmount(sessionInfo.dsq.getDenomination()).toFriendlyString() +
// ":" + sessionInfo.watch.elapsed(TimeUnit.SECONDS) + "s " + sessionInfo.dstx.getTx().getInputs().size() + "/10");
//}
}

public static void main(String[] args) throws Exception {
BriefLogFormatter.initVerbose();
System.out.println("Connecting to node");
BriefLogFormatter.initWithSilentBitcoinJ();
System.out.println("CoinJoinMonitor:");
final NetworkParameters params;
String filePrefix;
String checkpoints = null;
Expand All @@ -168,6 +166,8 @@ public static void main(String[] args) throws Exception {
checkpoints = "checkpoints.txt";
break;
}
System.out.println("Network: " + params.getNetworkName());
System.out.println("File Prefix: " + filePrefix);

report = new CoinJoinReport("", "", params);
kit = new WalletAppKit(params, new File("."), filePrefix) {
Expand Down Expand Up @@ -212,8 +212,9 @@ public void notifyNewBestBlock(StoredBlock block) throws VerificationException {
long currentTime = Utils.currentTimeSeconds();
long timeInterval = currentTime - startTime;
System.out.println("Time Elapsed: " + ((double)timeInterval / 3600) + " hrs");
System.out.println("dsqCount: " + queueSet.size());
System.out.println("txCount: " + mapDSTX.size());
System.out.println("dsqCount: " + queueSet.size());
System.out.println("txCount: " + mapDSTX.size());
System.out.printf("Completed Queues: %.1f%%%n",(double) mapDSTX.size() * 100 / queueSet.size());

calculateDSTX(timeInterval);

Expand All @@ -229,16 +230,13 @@ public Message onPreMessageReceived(Peer peer, Message m) {
dsqCount++;
CoinJoinQueue dsq = (CoinJoinQueue) m;
if (queueSet.add(dsq)) {
writeTime();
System.out.println("dsq: " + m);
// add to the pending sessions
pendingSessions.put(dsq.getProTxHash(), SessionInfo.start(dsq));
}
} else if (m instanceof Transaction) {
//System.out.println("tx: " + m);
// determine the type of transaction here
//Transaction tx = (Transaction) m;
// look for collateral tx and fees?
} else if (m instanceof CoinJoinBroadcastTx) {
writeTime();
System.out.println("dstx: " + m);
CoinJoinBroadcastTx dstx = (CoinJoinBroadcastTx) m;
if (mapDSTX.put(dstx.getTx().getTxId(), dstx) == null) {
Expand All @@ -250,13 +248,6 @@ public Message onPreMessageReceived(Peer peer, Message m) {
listDSTX.add(dstx);
}
txCount++;
} else if (m instanceof Block) {
//Block block = (Block) m;
//if (!mapBlocks.containsKey(block.getHash())) {
// if (mapBlocks.containsKey(block.getPrevBlockHash()))
// System.out.println("New Block Mined: " + m.getHash());
// mapBlocks.put(block.getHash(), block);
//}
}
return m;
}
Expand All @@ -269,4 +260,10 @@ public Message onPreMessageReceived(Peer peer, Message m) {
} catch (InterruptedException ignored) {}

}
static DateFormat format = DateFormat.getDateTimeInstance();

protected static void writeTime() {
System.out.print(format.format(new Date(Utils.currentTimeMillis())));
System.out.print(" ");
}
}
116 changes: 113 additions & 3 deletions examples/src/main/java/org/bitcoinj/examples/debug/CoinJoinReport.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import com.google.common.collect.Lists;
import org.bitcoinj.coinjoin.CoinJoin;
import org.bitcoinj.coinjoin.CoinJoinBroadcastTx;
import org.bitcoinj.coinjoin.CoinJoinClientOptions;
import org.bitcoinj.coinjoin.CoinJoinConstants;
import org.bitcoinj.core.Block;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.NetworkParameters;
Expand All @@ -42,6 +44,29 @@ public class CoinJoinReport extends Report {
HashMap<Sha256Hash, BlockInfo> blockMap = new HashMap<>();
HashMap<Sha256Hash, List<CoinJoinBroadcastTx>> blockTxMap = new HashMap<>();

static class BlockStats {
Sha256Hash blockHash;
int blockHeight;
long blockTime;
int mix10;
int mix1;
int mixTenth;
int mixHundredth;
int mixThousandth;

public BlockStats(Sha256Hash blockHash, int blockHeight, long blockTime, int mix10, int mix1, int mixTenth, int mixHundredth, int mixThousandth) {
this.blockHash = blockHash;
this.blockHeight = blockHeight;
this.blockTime = blockTime;
this.mix10 = mix10;
this.mix1 = mix1;
this.mixTenth = mixTenth;
this.mixHundredth = mixHundredth;
this.mixThousandth = mixThousandth;
}
}
ArrayList<BlockStats> stats = Lists.newArrayList();

public CoinJoinReport(String dashClientPath, String confPath, NetworkParameters params) {
super("coinjoin-report-", dashClientPath, confPath, params);
}
Expand All @@ -64,8 +89,9 @@ public void setChainLock(StoredBlock storedBlock) {
public void printReport() {
try {
FileWriter writer = new FileWriter(outputFile);
writer.append("Block Id, Block Number, 10, 1, 0.1, 0.01, 0.001\n");
writer.append("Block Id, Block Number, Block Time, 10, 1, 0.1, 0.01, 0.001, 15x0.01, 12x0.01+30x0.001\n");

stats = Lists.newArrayList();
for (BlockInfo blockInfo : blockList) {
Block block = blockInfo.storedBlock.getHeader();

Expand All @@ -86,15 +112,37 @@ public void printReport() {
}
}

String line = String.format("%s, %d, %d, %d, %d, %d, %d\n",
stats.add(new BlockStats(block.getHash(),
blockInfo.storedBlock.getHeight(),
blockInfo.storedBlock.getHeader().getTime().getTime(),
countDenom[0],
countDenom[1],
countDenom[2],
countDenom[3],
countDenom[4]));

long mix15xTenths = calculateMix15xHundredths(stats);
if (mix15xTenths != -1L) {
mix15xTenths /= 60000;
}
long mix12xHundredthsAnd30Thousandths = calculateMixWithHundredthsAndThousandths(stats, 12, 30);
if (mix12xHundredthsAnd30Thousandths != -1L) {
mix12xHundredthsAnd30Thousandths /= 60000;
}

String line = String.format("%s, %d, %s, %d, %d, %d, %d, %d, %f, %f\n",
block.getHash(),
blockInfo.storedBlock.getHeight(),
blockInfo.storedBlock.getHeader().getTime(),
countDenom[0],
countDenom[1],
countDenom[2],
countDenom[3],
countDenom[4]
countDenom[4],
mix15xTenths != -1 ? (double) mix15xTenths / 60 : -1,
mix12xHundredthsAnd30Thousandths != -1 ? (double) mix12xHundredthsAnd30Thousandths / 60: -1
);

writer.append(line);
}
writer.close();
Expand All @@ -108,4 +156,66 @@ public void printReport() {
throw new RuntimeException(e);
}
}

private long calculateMix15xHundredths(ArrayList<BlockStats> stats) {
try {
int count = stats.size();
int lastIndex = -1;
int mixingTx = 0;
for (int i = count - 1; i >= 0; i--) {
if (stats.get(i).mixHundredth != 0) {
if (lastIndex == -1)
lastIndex = i;
// limit estimate to max sessions per block
mixingTx += Math.min(stats.get(i).mixHundredth, CoinJoinClientOptions.getSessions());
if (mixingTx >= 15) {
return stats.get(lastIndex).blockTime - stats.get(i - 1).blockTime;
}
}
}
return -1;
} catch (IndexOutOfBoundsException e) {
return -1;
}
}
private long calculateMixWithHundredthsAndThousandths(ArrayList<BlockStats> stats, int hundredths, int thousandths) {
try {
int count = stats.size();
int lastIndex = -1;
int mixingTx = 0;
long mixingHundredths = -1;
for (int i = count - 1; i >= 0; i--) {
if (stats.get(i).mixHundredth != 0) {
if (lastIndex == -1)
lastIndex = i;
// limit estimate to max sessions per block
mixingTx += Math.min(stats.get(i).mixHundredth, CoinJoinClientOptions.getSessions());
if (mixingTx >= hundredths) {
mixingHundredths = stats.get(lastIndex).blockTime - stats.get(i - 1).blockTime;
break;
}
}
}
lastIndex = -1;
mixingTx = 0;
long mixingThousandths = -1;
for (int i = count - 1; i >= 0; i--) {
if (stats.get(i).mixThousandth != 0) {
if (lastIndex == -1)
lastIndex = i;
// limit estimate to max sessions per block
mixingTx += Math.min(stats.get(i).mixThousandth, CoinJoinClientOptions.getSessions());
if (mixingTx >= thousandths) {
mixingThousandths = stats.get(lastIndex).blockTime - stats.get(i - 1).blockTime;
break;
}
}
}
return mixingHundredths != -1 && mixingThousandths != -1 ?
Math.max(mixingHundredths, mixingThousandths) :
-1;
} catch (IndexOutOfBoundsException e) {
return -1;
}
}
}

0 comments on commit b5abdeb

Please sign in to comment.