diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index edab10153..c49d9a2f2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,14 +14,16 @@ jobs: fail-fast: false name: JAVA ${{ matrix.distribution }} ${{ matrix.java }} OS ${{ matrix.os }} Gradle ${{ matrix.gradle }} steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 + with: + submodules: recursive - name: Set up JDK - uses: actions/setup-java@v1 + uses: actions/setup-java@v4 with: + distribution: 'temurin' java-version: ${{ matrix.java }} - name: Build bls library run: | - git submodule update --init --recursive cd contrib/dashj-bls git apply catch_changes.patch mvn package -DskipTests -Dmaven.javadoc.skip=true diff --git a/core/src/main/java/org/bitcoinj/coinjoin/utils/CoinJoinManager.java b/core/src/main/java/org/bitcoinj/coinjoin/utils/CoinJoinManager.java index d65de3e6b..872fda4ad 100644 --- a/core/src/main/java/org/bitcoinj/coinjoin/utils/CoinJoinManager.java +++ b/core/src/main/java/org/bitcoinj/coinjoin/utils/CoinJoinManager.java @@ -296,7 +296,8 @@ public void close() { if (masternodeGroup != null) { masternodeGroup.removePreMessageReceivedEventListener(preMessageReceivedEventListener); } - // Ensure executor is shut down + + // Shut down the executor before nulling fields — queued tasks may still reference them. ExecutorService execToStop = null; lock.lock(); try { @@ -310,6 +311,8 @@ public void close() { if (execToStop != null) { execToStop.shutdown(); } + blockChain = null; + peerGroup = null; } public boolean isMasternodeOrDisconnectRequested(MasternodeAddress address) { @@ -547,12 +550,12 @@ public void processTransaction(Transaction tx) { if(!alreadyHave(item)) { getdata.addItem(item); } else { - log.info("coinjoin: DSQUEUE: already has {}", item.hash); + log.debug("coinjoin: DSQUEUE: already has {}", item.hash); } } if (!getdata.getItems().isEmpty()) { // This will cause us to receive a bunch of block or tx messages. - log.info(COINJOIN_EXTRA, "coinjoin: DSQUEUE: requesting {} dsq messages", getdata.getItems().size()); + log.debug(COINJOIN_EXTRA, "coinjoin: DSQUEUE: requesting {} dsq messages", getdata.getItems().size()); getdata.getItems().forEach( inventoryItem -> log.info(COINJOIN_EXTRA, "getdata: {}", inventoryItem.hash)); peer.sendMessage(getdata); diff --git a/core/src/main/java/org/bitcoinj/core/AbstractManager.java b/core/src/main/java/org/bitcoinj/core/AbstractManager.java index dfd37bde7..47b6f642e 100644 --- a/core/src/main/java/org/bitcoinj/core/AbstractManager.java +++ b/core/src/main/java/org/bitcoinj/core/AbstractManager.java @@ -308,6 +308,9 @@ public void setFilename(String filename) { autosaveToFile(new File(filename), DELAY_TIME, TimeUnit.MILLISECONDS, null); } + /** + * Typically called when DashSystem is shutting down. + */ public void close() { if (vFileManager != null) { shutdownAutosaveAndWait(); diff --git a/core/src/main/java/org/bitcoinj/core/DualBlockChain.java b/core/src/main/java/org/bitcoinj/core/DualBlockChain.java index bec3ede4b..3b50bd863 100644 --- a/core/src/main/java/org/bitcoinj/core/DualBlockChain.java +++ b/core/src/main/java/org/bitcoinj/core/DualBlockChain.java @@ -16,7 +16,9 @@ package org.bitcoinj.core; +import org.bitcoinj.core.listeners.DownloadProgressTracker; import org.bitcoinj.store.BlockStoreException; +import org.bitcoinj.utils.Threading; import javax.annotation.Nullable; @@ -105,4 +107,56 @@ public StoredBlock getBlock(int height) { public StoredBlock getChainHead() { return getLongestChain().chainHead; } + + PeerGroup peerGroup; + MyDownloadProgressTracker downloadProgressTracker; + + private static class MyDownloadProgressTracker extends DownloadProgressTracker { + volatile boolean headerDownloadCompleted = false; + volatile boolean blockDownloadCompleted = false; + MyDownloadProgressTracker(boolean preprocessingBeforeBlocks) { + super(preprocessingBeforeBlocks); + } + + @Override + public void doneHeaderDownload() { + super.doneHeaderDownload(); + headerDownloadCompleted = true; + } + + @Override + protected void doneDownload() { + super.doneDownload(); + blockDownloadCompleted = true; + } + } + + public void setPeerGroup(PeerGroup peerGroup, MasternodeSync masternodeSync) { + close(); + if (peerGroup != null) { + this.peerGroup = peerGroup; + downloadProgressTracker = new MyDownloadProgressTracker(masternodeSync.hasSyncFlag(MasternodeSync.SYNC_FLAGS.SYNC_BLOCKS_AFTER_PREPROCESSING)); + peerGroup.addHeadersDownloadedEventListener(Threading.USER_THREAD, downloadProgressTracker); + peerGroup.addHeadersDownloadStartedEventListener(Threading.USER_THREAD, downloadProgressTracker); + peerGroup.addBlocksDownloadedEventListener(Threading.USER_THREAD, downloadProgressTracker); + peerGroup.addChainDownloadStartedEventListener(Threading.USER_THREAD, downloadProgressTracker); + peerGroup.addMasternodeListDownloadListener(Threading.USER_THREAD, downloadProgressTracker); + } + } + + public boolean isInitialHeaderSyncComplete() { + return downloadProgressTracker != null && (downloadProgressTracker.headerDownloadCompleted || downloadProgressTracker.blockDownloadCompleted); + } + + public void close() { + if (peerGroup != null && downloadProgressTracker != null) { + peerGroup.removeHeadersDownloadedEventListener(downloadProgressTracker); + peerGroup.removeHeadersDownloadStartedEventListener(downloadProgressTracker); + peerGroup.removeBlocksDownloadedEventListener(downloadProgressTracker); + peerGroup.removeChainDownloadStartedEventListener(downloadProgressTracker); + peerGroup.removeMasternodeListDownloadedListener(downloadProgressTracker); + } + peerGroup = null; + downloadProgressTracker = null; + } } diff --git a/core/src/main/java/org/bitcoinj/core/MasternodeSync.java b/core/src/main/java/org/bitcoinj/core/MasternodeSync.java index 48f003e16..e24390243 100644 --- a/core/src/main/java/org/bitcoinj/core/MasternodeSync.java +++ b/core/src/main/java/org/bitcoinj/core/MasternodeSync.java @@ -126,6 +126,8 @@ public void close() { if (peerGroup != null) { peerGroup.removePreMessageReceivedEventListener(preMessageReceivedEventListener); } + peerGroup = null; + blockChain = null; } public MasternodeSync(Context context, boolean isLiteMode, boolean allowInstantSendInLiteMode) { diff --git a/core/src/main/java/org/bitcoinj/core/PeerGroup.java b/core/src/main/java/org/bitcoinj/core/PeerGroup.java index 700d42f4d..9b815f816 100644 --- a/core/src/main/java/org/bitcoinj/core/PeerGroup.java +++ b/core/src/main/java/org/bitcoinj/core/PeerGroup.java @@ -2044,7 +2044,7 @@ protected void handlePeerDeath(final Peer peer, @Nullable Throwable exception) { final Peer newDownloadPeer = selectDownloadPeer(peers); if (newDownloadPeer != null) { setDownloadPeer(newDownloadPeer); - if (downloadListener != null) { + if (downloadListener != null && vRunning) { startBlockChainDownloadFromPeer(newDownloadPeer); } } diff --git a/core/src/main/java/org/bitcoinj/core/SporkManager.java b/core/src/main/java/org/bitcoinj/core/SporkManager.java index dbf2c1094..eaab27843 100644 --- a/core/src/main/java/org/bitcoinj/core/SporkManager.java +++ b/core/src/main/java/org/bitcoinj/core/SporkManager.java @@ -72,6 +72,7 @@ private static void makeSporkDefinition(SporkId sporkId, long defaultValue) { @GuardedBy("lock") private final HashMap mapSporksCachedValues; @GuardedBy("lock") private final HashSet setSporkPubKeyIds = new HashSet<>(); + private PeerGroup peerGroup; private AbstractBlockChain blockChain; private MasternodeSync masternodeSync; private final Context context; @@ -91,6 +92,7 @@ public SporkManager(Context context) public void setBlockChain(AbstractBlockChain blockChain, @Nullable PeerGroup peerGroup, MasternodeSync masternodeSync) { this.blockChain = blockChain; this.masternodeSync = masternodeSync; + this.peerGroup = peerGroup; if (peerGroup != null) { peerGroup.addConnectedEventListener(peerConnectedEventListener); peerGroup.addPreMessageReceivedEventListener(SAME_THREAD, preMessageReceivedEventListener); @@ -102,11 +104,14 @@ public void clear() { mapSporksByHash.clear(); } - public void close(PeerGroup peerGroup) { + public void close() { if (peerGroup != null) { peerGroup.removeConnectedEventListener(peerConnectedEventListener); peerGroup.removePreMessageReceivedEventListener(preMessageReceivedEventListener); } + peerGroup = null; + blockChain = null; + masternodeSync = null; } void processSpork(Peer from, SporkMessage spork) { @@ -114,6 +119,8 @@ void processSpork(Peer from, SporkMessage spork) { // return; //disable all darksend/masternode related functionality // } + if (blockChain == null) return; // closed during shutdown + Sha256Hash hash = spork.getHash(); String logMessage = String.format("SPORK -- hash: %s id: %d (%s) value: %10d bestHeight: %d peer=%s:%d", hash, spork.getSporkId().value, String.format("%1$35s", spork.getSporkId().name()), diff --git a/core/src/main/java/org/bitcoinj/evolution/AbstractQuorumState.java b/core/src/main/java/org/bitcoinj/evolution/AbstractQuorumState.java index 6c7c83934..08091bcd7 100644 --- a/core/src/main/java/org/bitcoinj/evolution/AbstractQuorumState.java +++ b/core/src/main/java/org/bitcoinj/evolution/AbstractQuorumState.java @@ -554,7 +554,7 @@ public void removeEventListeners(AbstractBlockChain blockChain, PeerGroup peerGr blockChain.removeNewBestBlockListener(newBestBlockListener); blockChain.removeReorganizeListener(reorganizeListener); } - if (peerGroup != null) { + if (peerGroup != null) { peerGroup.removeConnectedEventListener(peerConnectedEventListener); peerGroup.removeChainDownloadStartedEventListener(chainDownloadStartedEventListener); peerGroup.removeHeadersDownloadStartedEventListener(headersDownloadStartedEventListener); @@ -597,6 +597,7 @@ public void notifyNewBestBlock(StoredBlock block) throws VerificationException { public final PeerConnectedEventListener peerConnectedEventListener = new PeerConnectedEventListener() { @Override public void onPeerConnected(Peer peer, int peerCount) { + if (peerGroup == null) return; // closed during shutdown downloadPeer = peerGroup.getDownloadPeer(); log.info("peer connected and setting download peer to {} with onPeerConnected", downloadPeer); } @@ -605,6 +606,7 @@ public void onPeerConnected(Peer peer, int peerCount) { final PeerDisconnectedEventListener peerDisconnectedEventListener = new PeerDisconnectedEventListener() { @Override public void onPeerDisconnected(Peer peer, int peerCount) { + if (peerGroup == null) return; // closed during shutdown if (downloadPeer == peer) { downloadPeer = peerGroup.getDownloadPeer(); log.info("setting download peer to {} with onPeerDisconnected, previously was {}", downloadPeer, peer); @@ -897,5 +899,7 @@ public void close() { retryFuture.cancel(true); retryFuture = null; } + peerGroup = null; + blockChain = null; } } diff --git a/core/src/main/java/org/bitcoinj/evolution/QuorumRotationState.java b/core/src/main/java/org/bitcoinj/evolution/QuorumRotationState.java index bee949fac..f642772ad 100644 --- a/core/src/main/java/org/bitcoinj/evolution/QuorumRotationState.java +++ b/core/src/main/java/org/bitcoinj/evolution/QuorumRotationState.java @@ -445,13 +445,20 @@ public boolean isSynced() { if (blockChain != null && !params.isDIP0024Active(blockChain.getBestChainHeight())) return true; - if(mnListAtH.getHeight() == -1) + if (mnListAtH.getHeight() == -1) return false; - if (peerGroup == null) + if (blockChain == null) return false; - int mostCommonHeight = peerGroup.getMostCommonHeight(); + if (!blockChain.isInitialHeaderSyncComplete()) { + return false; + } + + // Use local chain height instead of peerGroup.getMostCommonHeight() to avoid + // acquiring the PeerGroup lock, which can deadlock during shutdown or when the + // PeerGroup thread holds it while blocked on SPVBlockStore I/O. + int mostCommonHeight = blockChain.getBestChainHeight(); // determine when the last QR height was LLMQParameters llmqParameters = params.getLlmqs().get(llmqType); diff --git a/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListManager.java b/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListManager.java index ef57f00dd..7707929ff 100644 --- a/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListManager.java +++ b/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListManager.java @@ -453,8 +453,9 @@ public void close() { threadPool.shutdownNow(); // Send interrupt signal but don't wait } saveNow(); // Always save, regardless of thread pool state + peerGroup = null; + blockChain = null; super.close(); - } } diff --git a/core/src/main/java/org/bitcoinj/governance/GovernanceManager.java b/core/src/main/java/org/bitcoinj/governance/GovernanceManager.java index 0fd2f9218..a8c6b03d1 100644 --- a/core/src/main/java/org/bitcoinj/governance/GovernanceManager.java +++ b/core/src/main/java/org/bitcoinj/governance/GovernanceManager.java @@ -1719,5 +1719,6 @@ public void close() { peerGroup.removeGetDataEventListener(getDataEventListener); peerGroup.removePreMessageReceivedEventListener(preMessageReceivedEventListener); } + peerGroup = null; } } diff --git a/core/src/main/java/org/bitcoinj/manager/DashSystem.java b/core/src/main/java/org/bitcoinj/manager/DashSystem.java index 3207d77e5..a75ee28f8 100644 --- a/core/src/main/java/org/bitcoinj/manager/DashSystem.java +++ b/core/src/main/java/org/bitcoinj/manager/DashSystem.java @@ -58,6 +58,7 @@ public class DashSystem { public AbstractBlockChain blockChain; @Nullable public AbstractBlockChain headerChain; + DualBlockChain dualBlockChain; public SporkManager sporkManager; public MasternodePayments masternodePayments; public MasternodeSync masternodeSync; @@ -251,10 +252,13 @@ private void stopLLMQThread() { } } + /** + * close down DashSystem and free resources, typically called after the PeerGroup is shutdown + */ public void close() { if (initializedObjects) { log.info("Closing network spork configuration manager"); - sporkManager.close(peerGroup); + sporkManager.close(); log.info("Closing masternode synchronization state"); masternodeSync.close(); log.info("Closing masternode list manager (includes thread pool shutdown)"); @@ -285,6 +289,8 @@ public void close() { log.info("Closing header chain"); headerChain.close(); } + log.info("closing dual blockchain"); + dualBlockChain.close(); log.info("Clearing peer group reference"); peerGroup = null; } @@ -295,13 +301,14 @@ public void setPeerGroupAndBlockChain(PeerGroup peerGroup, AbstractBlockChain bl this.peerGroup = peerGroup; this.blockChain = blockChain; this.headerChain = headerChain; - DualBlockChain dualBlockChain = new DualBlockChain(headerChain, blockChain); + dualBlockChain = new DualBlockChain(headerChain, blockChain); hashStore = new HashStore(blockChain.getBlockStore()); blockChain.addNewBestBlockListener(newBestBlockListener); handleActivations(blockChain.getChainHead()); if (initializedObjects) { sporkManager.setBlockChain(blockChain, peerGroup, masternodeSync); masternodeSync.setBlockChain(blockChain, peerGroup, netFullfilledRequestManager, governanceManager); + dualBlockChain.setPeerGroup(peerGroup, masternodeSync); masternodeListManager.setBlockChain( dualBlockChain, peerGroup, diff --git a/core/src/main/java/org/bitcoinj/quorums/ChainLocksHandler.java b/core/src/main/java/org/bitcoinj/quorums/ChainLocksHandler.java index d9472483a..d12a7c668 100644 --- a/core/src/main/java/org/bitcoinj/quorums/ChainLocksHandler.java +++ b/core/src/main/java/org/bitcoinj/quorums/ChainLocksHandler.java @@ -203,10 +203,12 @@ public void processChainLockSignature(Peer peer, ChainLockSignature clsig) processNewChainLock(peer, clsig, hash); } - void processNewChainLock(Peer from, ChainLockSignature clsig, Sha256Hash hash) - { + void processNewChainLock(Peer from, ChainLockSignature clsig, Sha256Hash hash) { + if (blockChain == null) return; // closed during shutdown + lock.lock(); try { + if (blockChain == null) return; // re-check under lock if (seenChainLocks.put(hash, Utils.currentTimeMillis()) != null) { return; } diff --git a/core/src/main/java/org/bitcoinj/quorums/InstantSendManager.java b/core/src/main/java/org/bitcoinj/quorums/InstantSendManager.java index 527393534..6c2ea9f5e 100644 --- a/core/src/main/java/org/bitcoinj/quorums/InstantSendManager.java +++ b/core/src/main/java/org/bitcoinj/quorums/InstantSendManager.java @@ -132,6 +132,9 @@ public void close(PeerGroup peerGroup) { if (chainLocksHandler != null) { chainLocksHandler.removeChainLockListener(this.chainLockListener); } + blockChain = null; + peerGroup = null; + chainLocksHandler = null; wallets.forEach(wallet -> wallet.removeCoinsSentEventListener(coinsSentEventListener)); try { if (!scheduledExecutorService.awaitTermination(3000, TimeUnit.MILLISECONDS)) { diff --git a/core/src/main/java/org/bitcoinj/wallet/CoinJoinExtension.java b/core/src/main/java/org/bitcoinj/wallet/CoinJoinExtension.java index b7299807c..5a3edc57a 100644 --- a/core/src/main/java/org/bitcoinj/wallet/CoinJoinExtension.java +++ b/core/src/main/java/org/bitcoinj/wallet/CoinJoinExtension.java @@ -19,6 +19,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.common.util.concurrent.AtomicDouble; import com.google.protobuf.ByteString; import com.google.protobuf.CodedOutputStream; import net.jcip.annotations.GuardedBy; @@ -54,6 +55,7 @@ import java.security.SecureRandom; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Random; @@ -546,9 +548,20 @@ boolean isKeyUsed(byte[] pubKeyHash) { } public void refreshUnusedKeys() { + // Pre-compute used pubkey hashes outside the lock to avoid O(keys × txes × outputs) + // work while holding unusedKeysLock, which was causing severe lock contention. + Set usedPubKeyHashes = new HashSet<>(); + for (Transaction tx : wallet.getTransactions(true)) { + for (TransactionOutput output : tx.getOutputs()) { + if (ScriptPattern.isP2PKH(output.getScriptPubKey())) { + usedPubKeyHashes.add(ByteString.copyFrom( + ScriptPattern.extractHashFromP2PKH(output.getScriptPubKey()))); + } + } + } + List issuedKeys; - Set txes = wallet.getTransactions(true); - + unusedKeysLock.lock(); try { keyChainGroupLock.lock(); @@ -559,28 +572,14 @@ public void refreshUnusedKeys() { keyChainGroupLock.unlock(); } - issuedKeys.forEach(key -> { + for (IDeterministicKey key : issuedKeys) { unusedKeys.put(KeyId.fromBytes(key.getPubKeyHash()), (DeterministicKey) key); - keyUsage.put(key, false); - }); - - Stream usedKeys = issuedKeys.stream().filter(key -> { - boolean found = txes.stream().anyMatch(tx -> - tx.getOutputs().stream().anyMatch(output -> { - if (ScriptPattern.isP2PKH(output.getScriptPubKey())) { - byte[] publicKeyHash = ScriptPattern.extractHashFromP2PKH(output.getScriptPubKey()); - return Arrays.equals(publicKeyHash, key.getPubKeyHash()); - } else return false; - }) - ); - if (found) { - keyUsage.put(key, true); - } - return found; - } - ); - - usedKeys.forEach(key -> unusedKeys.remove(KeyId.fromBytes(key.getPubKeyHash()))); + boolean used = usedPubKeyHashes.contains(ByteString.copyFrom(key.getPubKeyHash())); + keyUsage.put(key, used); + if (used) { + unusedKeys.remove(KeyId.fromBytes(key.getPubKeyHash())); + } + } unusedKeys.forEach((keyId, key) -> log.info(COINJOIN_EXTRA, "unused key: {}", key)); keyUsage.forEach((key, used) -> { @@ -733,7 +732,9 @@ public String getKeyUsageReport() { } } - public double getMixingProgress() { + // this is the old algorithm + @Deprecated + public double getMixingProgress2() { double requiredRounds = rounds + 0.875; // 1 x 50% + 1 x 50%^2 + 1 x 50%^3 AtomicInteger totalInputs = new AtomicInteger(); AtomicInteger totalRounds = new AtomicInteger(); @@ -763,7 +764,52 @@ public double getMixingProgress() { }); }); double progress = totalInputs.get() != 0 ? totalRounds.get() / (requiredRounds * totalInputs.get()) : 0.0; - log.info("getMixingProgress: {} = {} / ({} * {})", progress, totalRounds.get(), requiredRounds, totalInputs.get()); + log.info("getMixingProgress2: {} = {} / ({} * {})", progress, totalRounds.get(), requiredRounds, totalInputs.get()); + return Math.max(0.0, Math.min(progress, 1.0)); + } + + public double getMixingProgress() { + double requiredRounds = rounds + 0.875; // 1 x 50% + 1 x 50%^2 + 1 x 50%^3 + AtomicInteger totalInputs = new AtomicInteger(); + AtomicDouble totalMixed = new AtomicDouble(); + getOutputs().forEach((denom, outputs) -> { + outputs.forEach(output -> { + // do not count mixing collateral for fees + if (denom >= 0) { + // getOutputs has a bug where non-denominated items are marked as denominated + TransactionOutPoint outPoint = new TransactionOutPoint(output.getParams(), output.getIndex(), output.getParentTransactionHash()); + int roundsMixed = ((WalletEx) wallet).getRealOutpointCoinJoinRounds(outPoint); + + if (roundsMixed >= rounds) { + if (wallet.isFullyMixed(output)) { + totalMixed.addAndGet(1.00); + } else { + totalMixed.addAndGet(((double)roundsMixed) / (roundsMixed + 1)); + } + totalInputs.addAndGet(1); + } else { + if (roundsMixed >= 0) { + totalInputs.addAndGet(1); + double percentMixedForInput = ((double) roundsMixed) / requiredRounds; + totalMixed.addAndGet(percentMixedForInput); + } + } + } else if (denom == -2) { + // estimate what the denominations would be: use greedy algorithm + AtomicInteger unmixedInputs = new AtomicInteger(0); + AtomicReference outputValue = new AtomicReference<>(output.getValue().subtract(CoinJoin.getCollateralAmount())); + CoinJoinClientOptions.getDenominations().forEach(coin -> { + while (outputValue.get().subtract(coin).isGreaterThan(Coin.ZERO)) { + unmixedInputs.getAndIncrement(); + outputValue.set(outputValue.get().subtract(coin)); + } + }); + totalInputs.set(totalInputs.get() + unmixedInputs.get()); + } + }); + }); + double progress = totalInputs.get() != 0 ? totalMixed.get() / totalInputs.get() : 0.0; + log.info("getMixingProgress: {} = {} / {}", progress, totalMixed.get(), totalInputs.get()); return Math.max(0.0, Math.min(progress, 1.0)); } diff --git a/core/src/test/java/org/bitcoinj/wallet/LargeCoinJoinWalletTest.java b/core/src/test/java/org/bitcoinj/wallet/LargeCoinJoinWalletTest.java index 6aea2120c..4ca92e8f0 100644 --- a/core/src/test/java/org/bitcoinj/wallet/LargeCoinJoinWalletTest.java +++ b/core/src/test/java/org/bitcoinj/wallet/LargeCoinJoinWalletTest.java @@ -93,8 +93,12 @@ public void balanceAndMixingProgressTest() { info("getBalance(COINJOIN): {}", watch1); Stopwatch watch2 = Stopwatch.createStarted(); - assertEquals(1.00, wallet.getCoinJoin().getMixingProgress(), 0.001); - info("getMixingProgress: {}", watch2); + assertEquals(1.00, wallet.getCoinJoin().getMixingProgress2(), 0.001); + info("getMixingProgress2: {}", watch2); + + Stopwatch watch3 = Stopwatch.createStarted(); + assertEquals(0.9987573607826772, wallet.getCoinJoin().getMixingProgress(), 0.001); + info("getMixingProgress: {}", watch3); } @Test diff --git a/examples/src/main/java/org/bitcoinj/examples/DumpMasternodeList.java b/examples/src/main/java/org/bitcoinj/examples/DumpMasternodeList.java new file mode 100644 index 000000000..3a9d84019 --- /dev/null +++ b/examples/src/main/java/org/bitcoinj/examples/DumpMasternodeList.java @@ -0,0 +1,128 @@ +/* + * Copyright 2024 Dash Core Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bitcoinj.examples; + +import com.google.common.util.concurrent.SettableFuture; +import org.bitcoinj.core.BlockChain; +import org.bitcoinj.core.Context; +import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.core.Peer; +import org.bitcoinj.core.PeerGroup; +import org.bitcoinj.core.Sha256Hash; +import org.bitcoinj.evolution.GetSimplifiedMasternodeListDiff; +import org.bitcoinj.evolution.Masternode; +import org.bitcoinj.evolution.MasternodeType; +import org.bitcoinj.evolution.SimplifiedMasternodeListDiff; +import org.bitcoinj.net.discovery.DnsDiscovery; +import org.bitcoinj.params.MainNetParams; +import org.bitcoinj.params.TestNet3Params; +import org.bitcoinj.store.BlockStore; +import org.bitcoinj.store.BlockStoreException; +import org.bitcoinj.store.MemoryBlockStore; +import org.bitcoinj.utils.BriefLogFormatter; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.bitcoinj.utils.Threading.SAME_THREAD; + +public class DumpMasternodeList { + public static void main(String[] args) throws BlockStoreException, ExecutionException, InterruptedException { + if (args.length < 2) { + System.out.println("DumpMasternodeList network blockHash"); + System.out.println(" one or more arguments are missing!"); + return; + } + BriefLogFormatter.init(); + String network = args[0]; + String blockHash = args[1]; + + NetworkParameters params; + switch (network) { + case "testnet": + params = TestNet3Params.get(); + break; + default: + params = MainNetParams.get(); + break; + } + Context context = Context.getOrCreate(params); + BlockStore blockStore = new MemoryBlockStore(params); + BlockChain chain = new BlockChain(params, blockStore); + PeerGroup peerGroup = new PeerGroup(params, chain); + peerGroup.addPeerDiscovery(new DnsDiscovery(params)); + peerGroup.setUseLocalhostPeerWhenPossible(false); + + peerGroup.start(); + peerGroup.waitForPeers(10).get(); + SettableFuture mnlistdiffReceivedFuture = SettableFuture.create(); + peerGroup.addPreMessageReceivedEventListener(SAME_THREAD, (peer1, m) -> { + try { + if (m instanceof SimplifiedMasternodeListDiff) { + System.out.println("Received mnlistdiff..."); + File dumpFile = new File(params.getNetworkName() + "-mnlist.dat"); + try (OutputStream stream = new FileOutputStream(dumpFile)) { + stream.write(m.bitcoinSerialize()); + } + SimplifiedMasternodeListDiff diff = (SimplifiedMasternodeListDiff)m; + AtomicInteger countLegacy = new AtomicInteger(); + AtomicInteger countEnabled = new AtomicInteger(); + ArrayList evoNodes = new ArrayList<>(); + diff.getMnList().forEach(entry -> { + if (entry.isValid()) { + countLegacy.addAndGet((short) (entry.getVersion() == 1 ? 1 : 0)); + countEnabled.addAndGet(1); + if (MasternodeType.getMasternodeType(entry.getType()) == MasternodeType.HIGHPERFORMANCE) { + evoNodes.add(entry); + } + //System.out.println(countEnabled + " " + entry.getService().getAddr().getHostAddress()); + } + }); + System.out.printf("Total: %d, Legacy: %d\n", countEnabled.get(), countLegacy.get()); + System.out.println("HP Masternode List"); + evoNodes.forEach(entry -> System.out.println("\"" + entry.getService().getAddr().getHostAddress() + "\",")); + mnlistdiffReceivedFuture.set(true); + return null; + } + } catch (FileNotFoundException e) { + System.out.println("cannot find the file to write to"); + mnlistdiffReceivedFuture.setException(e); + throw new RuntimeException(e); + } catch (IOException e) { + System.out.println("IO Error"); + e.printStackTrace(); + mnlistdiffReceivedFuture.setException(e); + throw new RuntimeException(e); + } + + return m; + }); + Peer peer = peerGroup.getDownloadPeer(); + + peer.sendMessage(new GetSimplifiedMasternodeListDiff(params.getGenesisBlock().getHash(), Sha256Hash.wrap(blockHash))); + + + mnlistdiffReceivedFuture.get(); + peerGroup.stopAsync(); + } +}