8000 fix: improve getTransactions in Wallet by HashEngineering · Pull Request #276 · dashpay/dashj · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

fix: improve getTransactions in Wallet #276

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .run/WalletTool CJ Mix.run.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="WalletTool CJ Mix" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.bitcoinj.tools.WalletTool" />
<module name="dashj-feature-coinjoin.tools.main" />
<option name="PROGRAM_PARAMETERS" value="mix --wallet=coinjoin.testnet.wallet --net=TEST --debuglog --amount=0.15 --sessions=8 --rounds=1 --multi-session" />
<module name="dashj-parent.dashj-tools.main" />
<option name="PROGRAM_PARAMETERS" value="mix --wallet=coinjoin.testnet.wallet --net=TEST --debuglog --amount=0.50 --sessions=6 --rounds=1" />
<option name="VM_PARAMETERS" value="-Djava.library.path=contrib/dashj-bls/bls/target/cmake" />
<extension name="coverage">
<pattern>
Expand Down
2 changes: 1 addition & 1 deletion .run/WalletTool CJ Sync.run.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<configuration default="false" name="WalletTool CJ Sync" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.bitcoinj.tools.WalletTool" />
<module name="dashj-parent.dashj-tools.main" />
<option name="PROGRAM_PARAMETERS" value="sync --wallet=coinjoin.testnet.wallet --net=TEST" />
<option name="PROGRAM_PARAMETERS" value="sync --wallet=coinjoin.testnet.wallet --net=TEST --debuglog" />
<option name="VM_PARAMETERS" value="-Djava.library.path=contrib/dashj-bls/bls/target/cmake" />
<extension name="coverage">
<pattern>
Expand Down
4 changes: 2 additions & 2 deletions .run/WalletTool CJ address (devnet).run.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="WalletTool CJ address (devnet)" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.bitcoinj.tools.WalletTool" />
<module name="dashj-master-three.tools.main" />
<option name="PROGRAM_PARAMETERS" value="current-receive-addr --wallet=coinjoin.bintang.wallet --net=DEVNET" />
<module name="dashj-parent.dashj-tools.main" />
<option name="PROGRAM_PARAMETERS" value="current-receive-addr --wallet=coinjoin.testnet.wallet --net=TEST" />
<option name="VM_PARAMETERS" value="-Djava.library.path=contrib/dashj-bls/bls/target/cmake" />
<extension name="coverage">
<pattern>
Expand Down
36 changes: 35 additions & 1 deletion core/src/main/java/org/bitcoinj/wallet/Wallet.java
Original file line number Diff line number Diff line change
Expand Up @@ -3264,7 +3264,41 @@ public void run() {
public Set<Transaction> getTransactions(boolean includeDead) {
lock.lock();
try {
Set<Transaction> all = new HashSet<>();
int capacity = unspent.size() + spent.size() + pending.size() + (includeDead ? dead.size(): 0);
Set<Transaction> all = new HashSet<>(capacity);
all.addAll(unspent.values());
all.addAll(spent.values());
all.addAll(pending.values());
if (includeDead)
all.addAll(dead.values());
return all;
} finally {
lock.unlock();
}
}

/**
* Returns the count of all transactions in the wallet.
* @param includeDead If true, transactions that were overridden by a double spend are included.
*/
public int getTransactionCount(boolean includeDead) {
lock.lock();
try {
return unspent.size() + spent.size() + pending.size() + (includeDead ? dead.size(): 0);
} finally {
lock.unlock();
}
}

/**
* Returns a list of all transactions in the wallet.
* @param includeDead If true, transactions that were overridden by a double spend are included.
*/
public Collection<Transaction> getTransactionList(boolean includeDead) {
lock.lock();
try {
int capacity = unspent.size() + spent.size() + pending.size() + (includeDead ? dead.size(): 0);
ArrayList<Transaction> all = new ArrayList<>(capacity);
all.addAll(unspent.values());
all.addAll(spent.values());
all.addAll(pending.values());
Expand Down
149 changes: 149 additions & 0 deletions core/src/test/java/org/bitcoinj/wallet/LargeCoinJoinWalletTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* Copyright 2025 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.wallet;

import com.google.common.base.Stopwatch;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Context;
import org.bitcoinj.params.TestNet3Params;
import org.bitcoinj.utils.BriefLogFormatter;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;

import static org.junit.Assert.assertEquals;

public class LargeCoinJoinWalletTest {
private WalletEx wallet;
private static Logger log = LoggerFactory.getLogger(LargeCoinJoinWalletTest.class);
TestNet3Params PARAMS = TestNet3Params.get();
Context CONTEXT = new Context(PARAMS);

@Before
public void setup() {
BriefLogFormatter.initWithSilentBitcoinJ();
try (InputStream is = getClass().getResourceAsStream("coinjoin-large.wallet")) {
Stopwatch watch = Stopwatch.createStarted();
wallet = (WalletEx) new WalletProtobufSerializer().readWallet(is);
info("loading wallet: {}; {} transactions", watch, wallet.getTransactionCount(true));
} catch (IOException e) {
throw new RuntimeException(e);
} catch (UnreadableWalletException e) {
throw new RuntimeException(e);
}
}

@Test
public void coinJoinInfoTest() {
info("rounds: {}", wallet.coinjoin.rounds);

Stopwatch watch0 = Stopwatch.createStarted();
info("key usage: {}; time: {}", wallet.coinjoin.getKeyUsage(), watch0);
Stopwatch watch1 = Stopwatch.createStarted();
wallet.coinjoin.refreshUnusedKeys();
info("unused key count: {}; time: {}", wallet.coinjoin.getUnusedKeyCount(), watch1);
}
Comment on lines +54 to +63
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add assertions to validate CoinJoin information.

This test method executes operations but doesn't assert that the results meet any expected criteria. Without assertions, it functions more as a performance benchmark than a validation test.

@Test
public void coinJoinInfoTest() {
    info("rounds: {}", wallet.coinjoin.rounds);

    Stopwatch watch0 = Stopwatch.createStarted();
-   info("key usage: {}; time: {}", wallet.coinjoin.getKeyUsage(), watch0);
+   int keyUsage = wallet.coinjoin.getKeyUsage();
+   info("key usage: {}; time: {}", keyUsage, watch0);
+   // Assert expected key usage values
+   assertTrue("Key usage should be positive", keyUsage > 0);
    
    Stopwatch watch1 = Stopwatch.createStarted();
    wallet.coinjoin.refreshUnusedKeys();
-   info("unused key count: {}; time: {}", wallet.coinjoin.getUnusedKeyCount(), watch1);
+   int unusedKeyCount = wallet.coinjoin.getUnusedKeyCount();
+   info("unused key count: {}; time: {}", unusedKeyCount, watch1);
+   // Assert expected unused key count
+   assertTrue("Unused key count should be positive after refresh", unusedKeyCount > 0);
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Test
public void coinJoinInfoTest() {
info("rounds: {}", wallet.coinjoin.rounds);
Stopwatch watch0 = Stopwatch.createStarted();
info("key usage: {}; time: {}", wallet.coinjoin.getKeyUsage(), watch0);
Stopwatch watch1 = Stopwatch.createStarted();
wallet.coinjoin.refreshUnusedKeys();
info("unused key count: {}; time: {}", wallet.coinjoin.getUnusedKeyCount(), watch1);
}
@Test
public void coinJoinInfoTest() {
info("rounds: {}", wallet.coinjoin.rounds);
Stopwatch watch0 = Stopwatch.createStarted();
int keyUsage = wallet.coinjoin.getKeyUsage();
info("key usage: {}; time: {}", keyUsage, watch0);
// Assert expected key usage values
assertTrue("Key usage should be positive", keyUsage > 0);
Stopwatch watch1 = Stopwatch.createStarted();
wallet.coinjoin.refreshUnusedKeys();
int unusedKeyCount = wallet.coinjoin.getUnusedKeyCount();
info("unused key count: {}; time: {}", unusedKeyCount, watch1);
// Assert expected unused key count
assertTrue("Unused key count should be positive after refresh", unusedKeyCount > 0);
}


@Test
public void balanceAndMixingProgressTest() {
Stopwatch watch0 = Stopwatch.createStarted();
assertEquals(Coin.valueOf(13662399906L), wallet.getBalance(Wallet.BalanceType.ESTIMATED));
info("getBalance(ESTIMATED): {}", watch0);

Stopwatch watch1 = Stopwatch.createStarted();
assertEquals(Coin.valueOf(13634336342L), wallet.getBalance(Wallet.BalanceType.COINJOIN));
info("getBalance(COINJOIN): {}", watch1);

Stopwatch watch2 = Stopwatch.createStarted();
assertEquals(1.00, wallet.getCoinJoin().getMixingProgress(), 0.001);
info("getMixingProgress: {}", watch2);
}

@Test
public void transactionReportTest() {
Stopwatch watch0 = Stopwatch.createStarted();
wallet.getTransactionReport();
info("getTransactionReport: {}", watch0);

Stopwatch watch1 = Stopwatch.createStarted();
wallet.getTransactionReport();
info("getTransactionReport: {}", watch1);
}
Comment on lines +80 to +89
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add validation for transaction report test.

This test runs the transaction report twice but doesn't validate the output. Consider adding assertions to verify the report's correctness or at least checking for non-null results.

@Test
public void transactionReportTest() {
    Stopwatch watch0 = Stopwatch.createStarted();
-   wallet.getTransactionReport();
+   String report1 = wallet.getTransactionReport();
    info("getTransactionReport: {}", watch0);
+   
+   // Validate that the report is properly generated
+   assertNotNull("Transaction report should not be null", report1);
+   assertFalse("Transaction report should not be empty", report1.isEmpty());

    Stopwatch watch1 = Stopwatch.createStarted();
-   wallet.getTransactionReport();
+   String report2 = wallet.getTransactionReport();
    info("getTransactionReport: {}", watch1);
+   
+   // Ensure the reports are consistent
+   assertEquals("Transaction reports should be consistent", report1, report2);
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Test
public void transactionReportTest() {
Stopwatch watch0 = Stopwatch.createStarted();
wallet.getTransactionReport();
info("getTransactionReport: {}", watch0);
Stopwatch watch1 = Stopwatch.createStarted();
wallet.getTransactionReport();
info("getTransactionReport: {}", watch1);
}
@Test
public void transactionReportTest() {
Stopwatch watch0 = Stopwatch.createStarted();
String report1 = wallet.getTransactionReport();
info("getTransactionReport: {}", watch0);
// Validate that the report is properly generated
assertNotNull("Transaction report should not be null", report1);
assertFalse("Transaction report should not be empty", report1.isEmpty());
Stopwatch watch1 = Stopwatch.createStarted();
String report2 = wallet.getTransactionReport();
info("getTransactionReport: {}", watch1);
// Ensure the reports are consistent
assertEquals("Transaction reports should be consistent", report1, report2);
}


@Test
public void walletToStringTest() {
Stopwatch watch0 = Stopwatch.createStarted();
wallet.toString(true, false, true, null);
info("wallet.toString: {}", watch0);

Stopwatch watch1 = Stopwatch.createStarted();
wallet.toString(true, false, true, null);
info("wallet.toString: {}", watch1);

Stopwatch watch2 = Stopwatch.createStarted();
wallet.coinjoin.toString();
info("wallet.coinjoin.toString: {}", watch2);

Stopwatch watch3 = Stopwatch.createStarted();
wallet.coinjoin.toString(true, false, null);
info("wallet.coinjoin.toString(true, false, null): {}", watch3);
}
Comment on lines +91 to +108
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add validation to the toString test method.

Similar to the transaction report test, this method exercises various toString methods but doesn't validate their output. Consider adding basic assertions to ensure the methods return meaningful results.

@Test
public void walletToStringTest() {
    Stopwatch watch0 = Stopwatch.createStarted();
-   wallet.toString(true, false, true, null);
+   String walletStr1 = wallet.toString(true, false, true, null);
    info("wallet.toString: {}", watch0);
+   assertNotNull("Wallet toString should not be null", walletStr1);
+   assertFalse("Wallet toString should not be empty", walletStr1.isEmpty());

    Stopwatch watch1 = Stopwatch.createStarted();
-   wallet.toString(true, false, true, null);
+   String walletStr2 = wallet.toString(true, false, true, null);
    info("wallet.toString: {}", watch1);
+   assertEquals("Wallet toString should be consistent", walletStr1, walletStr2);

    Stopwatch watch2 = Stopwatch.createStarted();
-   wallet.coinjoin.toString();
+   String coinjoinStr = wallet.coinjoin.toString();
    info("wallet.coinjoin.toString: {}", watch2);
+   assertNotNull("CoinJoin toString should not be null", coinjoinStr);
+   assertFalse("CoinJoin toString should not be empty", coinjoinStr.isEmpty());

    Stopwatch watch3 = Stopwatch.createStarted();
-   wallet.coinjoin.toString(true, false, null);
+   String coinjoinDetailedStr = wallet.coinjoin.toString(true, false, null);
    info("wallet.coinjoin.toString(true, false, null): {}", watch3);
+   assertNotNull("CoinJoin detailed toString should not be null", coinjoinDetailedStr);
+   assertFalse("CoinJoin detailed toString should not be empty", coinjoinDetailedStr.isEmpty());
}


@Test
public void getTransactionsTest() {
Stopwatch watch0 = Stopwatch.createStarted();
wallet.getTransactionCount(true);
info("wallet.getTransactionCount: {}", watch0);

Stopwatch watch1 = Stopwatch.createStarted();
wallet.getTransactions(true);
info("wallet.getTransactions: {}", watch1);

Stopwatch watch2 = Stopwatch.createStarted();
wallet.getTransactionList(true);
info("wallet.getTransactionList: {}", watch2);

Stopwatch watch3 = Stopwatch.createStarted();
wallet.getWalletTransactions();
info("wallet.getWalletTransactions(): {}", watch3);
}
Comment on lines +110 to +127
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add assertions to validate transaction retrieval methods.

This method tests the new transaction retrieval methods but doesn't verify their correctness. Add assertions to ensure the methods return the expected results.

@Test
public void getTransactionsTest() {
    Stopwatch watch0 = Stopwatch.createStarted();
-   wallet.getTransactionCount(true);
+   int transactionCount = wallet.getTransactionCount(true);
    info("wallet.getTransactionCount: {}", watch0);
+   assertTrue("Transaction count should be positive", transactionCount > 0);

    Stopwatch watch1 = Stopwatch.createStarted();
-   wallet.getTransactions(true);
+   Set<Transaction> transactions = wallet.getTransactions(true);
    info("wallet.getTransactions: {}", watch1);
+   assertNotNull("Transactions set should not be null", transactions);
+   assertEquals("Transaction count should match set size", transactionCount, transactions.size());

    Stopwatch watch2 = Stopwatch.createStarted();
-   wallet.getTransactionList(true);
+   List<Transaction> transactionList = wallet.getTransactionList(true);
    info("wallet.getTransactionList: {}", watch2);
+   assertNotNull("Transaction list should not be null", transactionList);
+   assertEquals("Transaction list size should match count", transactionCount, transactionList.size());

    Stopwatch watch3 = Stopwatch.createStarted();
-   wallet.getWalletTransactions();
+   Iterable<WalletTransaction> walletTransactions = wallet.getWalletTransactions();
    info("wallet.getWalletTransactions(): {}", watch3);
+   assertNotNull("Wallet transactions should not be null", walletTransactions);
+   
+   // Count the wallet transactions
+   int walletTransactionCount = 0;
+   for (WalletTransaction ignored : walletTransactions) {
+       walletTransactionCount++;
+   }
+   assertTrue("Wallet should contain transactions", walletTransactionCount > 0);
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Test
public void getTransactionsTest() {
Stopwatch watch0 = Stopwatch.createStarted();
wallet.getTransactionCount(true);
info("wallet.getTransactionCount: {}", watch0);
Stopwatch watch1 = Stopwatch.createStarted();
wallet.getTransactions(true);
info("wallet.getTransactions: {}", watch1);
Stopwatch watch2 = Stopwatch.createStarted();
wallet.getTransactionList(true);
info("wallet.getTransactionList: {}", watch2);
Stopwatch watch3 = Stopwatch.createStarted();
wallet.getWalletTransactions();
info("wallet.getWalletTransactions(): {}", watch3);
}
@Test
public void getTransactionsTest() {
Stopwatch watch0 = Stopwatch.createStarted();
int transactionCount = wallet.getTransactionCount(true);
info("wallet.getTransactionCount: {}", watch0);
assertTrue("Transaction count should be positive", transactionCount > 0);
Stopwatch watch1 = Stopwatch.createStarted();
Set<Transaction> transactions = wallet.getTransactions(true);
info("wallet.getTransactions: {}", watch1);
assertNotNull("Transactions set should not be null", transactions);
assertEquals("Transaction count should match set size", transactionCount, transactions.size());
Stopwatch watch2 = Stopwatch.createStarted();
List<Transaction> transactionList = wallet.getTransactionList(true);
info("wallet.getTransactionList: {}", watch2);
assertNotNull("Transaction list should not be null", transactionList);
assertEquals("Transaction list size should match count", transactionCount, transactionList.size());
Stopwatch watch3 = Stopwatch.createStarted();
Iterable<WalletTransaction> walletTransactions = wallet.getWalletTransactions();
info("wallet.getWalletTransactions(): {}", watch3);
assertNotNull("Wallet transactions should not be null", walletTransactions);
// Count the wallet transactions
int walletTransactionCount = 0;
for (WalletTransaction ignored : walletTransactions) {
walletTransactionCount++;
}
assertTrue("Wallet should contain transactions", walletTransactionCount > 0);
}


public static void info(String format, Object... args) {
log("INFO", format, args);
}

public static void error(String format, Object... args) {
log("ERROR", format, args);
}

private static void log(String level, String format, Object... args) {
String timestamp = java.time.LocalDateTime.now().toString();
String message = formatMessage(format, args);
System.out.printf("%s [%s] %s%n", timestamp, level, message);
}

private static String formatMessage(String format, Object... args) {
for (Object arg : args) {
format = format.replaceFirst("\\{\\}", arg == null ? "null" : arg.toString());
}
return format;
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.bitcoinj.wallet.DeterministicSeed;
import org.bitcoinj.wallet.KeyChainGroup;
import org.bitcoinj.wallet.Wallet;
import org.bitcoinj.wallet.WalletEx;
import org.bitcoinj.wallet.authentication.AuthenticationGroupExtension;

import java.io.File;
Expand Down Expand Up @@ -84,12 +85,13 @@ public static void main(String[] args) throws Exception {
.build();

// The wallet class provides a easy fromSeed() function that loads a new wallet from a given seed.
Wallet wallet = new Wallet(params, keyChainGroup);
WalletEx wallet = new WalletEx(params, keyChainGroup);
AuthenticationGroupExtension mnext = new AuthenticationGroupExtension(wallet);
mnext.addKeyChain(seed, factory.masternodeOwnerDerivationPath(), AuthenticationKeyChain.KeyChainType.MASTERNODE_OWNER);
mnext.addKeyChain(seed, factory.masternodeVotingDerivationPath(), AuthenticationKeyChain.KeyChainType.MASTERNODE_VOTING);
mnext.addKeyChain(seed, factory.masternodeOperatorDerivationPath(), AuthenticationKeyChain.KeyChainType.MASTERNODE_OPERATOR);
wallet.addExtension(mnext);
wallet.initializeCoinJoin(0);

// Because we are importing an existing wallet which might already have transactions we must re-download the blockchain to make the wallet picks up these transactions
// You can find some information about this in the guides: https://bitcoinj.github.io/working-with-the-wallet#setup
Expand Down
Loading
0