From b5abdeb808914c75a10fda86e26aafec202dda40 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Tue, 13 Aug 2024 17:52:17 -0700 Subject: [PATCH] Feat(coinjoin): improve coinjoin monitor (#224) * 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 --- .../progress/MixingProgressTracker.java | 3 + .../coinjoin/utils/CoinJoinReporter.java | 1 + .../coinjoin/CoinJoinSessionTest.java | 2 +- .../bitcoinj/examples/CoinJoinMonitor.java | 57 ++++----- .../examples/debug/CoinJoinReport.java | 116 +++++++++++++++++- 5 files changed, 145 insertions(+), 34 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/coinjoin/progress/MixingProgressTracker.java b/core/src/main/java/org/bitcoinj/coinjoin/progress/MixingProgressTracker.java index 1fb451931..ebc35e1bf 100644 --- a/core/src/main/java/org/bitcoinj/coinjoin/progress/MixingProgressTracker.java +++ b/core/src/main/java/org/bitcoinj/coinjoin/progress/MixingProgressTracker.java @@ -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; diff --git a/core/src/main/java/org/bitcoinj/coinjoin/utils/CoinJoinReporter.java b/core/src/main/java/org/bitcoinj/coinjoin/utils/CoinJoinReporter.java index 1ed69aefb..19ceac0a3 100644 --- a/core/src/main/java/org/bitcoinj/coinjoin/utils/CoinJoinReporter.java +++ b/core/src/main/java/org/bitcoinj/coinjoin/utils/CoinJoinReporter.java @@ -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; diff --git a/core/src/test/java/org/bitcoinj/coinjoin/CoinJoinSessionTest.java b/core/src/test/java/org/bitcoinj/coinjoin/CoinJoinSessionTest.java index b6d95a676..1a418b6d5 100644 --- a/core/src/test/java/org/bitcoinj/coinjoin/CoinJoinSessionTest.java +++ b/core/src/test/java/org/bitcoinj/coinjoin/CoinJoinSessionTest.java @@ -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); diff --git a/examples/src/main/java/org/bitcoinj/examples/CoinJoinMonitor.java b/examples/src/main/java/org/bitcoinj/examples/CoinJoinMonitor.java index 15ff7ffd8..8a93004d1 100644 --- a/examples/src/main/java/org/bitcoinj/examples/CoinJoinMonitor.java +++ b/examples/src/main/java/org/bitcoinj/examples/CoinJoinMonitor.java @@ -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; @@ -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; /** @@ -131,27 +129,27 @@ private static void calculateDSTX(long timeInterval) { System.out.println("Denominations Mixed ----------------------------"); for (Map.Entry 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; @@ -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) { @@ -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); @@ -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) { @@ -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; } @@ -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(" "); + } } diff --git a/examples/src/main/java/org/bitcoinj/examples/debug/CoinJoinReport.java b/examples/src/main/java/org/bitcoinj/examples/debug/CoinJoinReport.java index d5d433e59..8077e119f 100644 --- a/examples/src/main/java/org/bitcoinj/examples/debug/CoinJoinReport.java +++ b/examples/src/main/java/org/bitcoinj/examples/debug/CoinJoinReport.java @@ -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; @@ -42,6 +44,29 @@ public class CoinJoinReport extends Report { HashMap blockMap = new HashMap<>(); HashMap> 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 stats = Lists.newArrayList(); + public CoinJoinReport(String dashClientPath, String confPath, NetworkParameters params) { super("coinjoin-report-", dashClientPath, confPath, params); } @@ -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(); @@ -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(); @@ -108,4 +156,66 @@ public void printReport() { throw new RuntimeException(e); } } + + private long calculateMix15xHundredths(ArrayList 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 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; + } + } }