From 3263a9145b8891b9cc6af147b94f656948f995ac Mon Sep 17 00:00:00 2001 From: Alva Swanson Date: Mon, 11 Nov 2024 00:58:01 +0000 Subject: [PATCH 1/2] core: Add dependency to bitcoind:regtest (integration source set) --- core/build.gradle | 4 +- gradle/verification-metadata.xml | 343 ++++++++++++++++++++++++++++++- settings.gradle | 2 +- 3 files changed, 343 insertions(+), 6 deletions(-) diff --git a/core/build.gradle b/core/build.gradle index 6e0f86bab9..b28c4c9aeb 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -63,9 +63,11 @@ dependencies { testCompileOnly libs.lombok testImplementation libs.natpryce.make.it.easy - integrationTestImplementation libs.junit.jupiter integrationTestAnnotationProcessor libs.lombok integrationTestCompileOnly libs.lombok + integrationTestImplementation('bitcoind:regtest') { + exclude(module: 'kotlin-stdlib-jdk8') + } } test { diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 4ad614144d..c90c0402df 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -25,6 +25,14 @@ + + + + + + + + @@ -33,11 +41,24 @@ + + + + + + + + + + + + + @@ -633,6 +654,14 @@ + + + + + + + + @@ -665,6 +694,11 @@ + + + + + @@ -762,6 +796,14 @@ + + + + + + + + @@ -787,6 +829,11 @@ + + + + + @@ -954,6 +1001,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1082,6 +1184,14 @@ + + + + + + + + @@ -1410,6 +1520,14 @@ + + + + + + + + @@ -1450,6 +1568,14 @@ + + + + + + + + @@ -1458,11 +1584,24 @@ + + + + + + + + + + + + + @@ -1722,6 +1861,19 @@ + + + + + + + + + + + + + @@ -1738,6 +1890,14 @@ + + + + + + + + @@ -1746,6 +1906,14 @@ + + + + + + + + @@ -2357,6 +2525,14 @@ + + + + + + + + @@ -2811,6 +2987,19 @@ + + + + + + + + + + + + + @@ -3098,6 +3287,22 @@ + + + + + + + + + + + + + + + + @@ -3107,11 +3312,27 @@ - - + + + + + + + + + + + + + + + + + + - - + + @@ -3130,6 +3351,14 @@ + + + + + + + + @@ -3146,6 +3375,14 @@ + + + + + + + + @@ -3255,6 +3492,14 @@ + + + + + + + + @@ -3271,6 +3516,14 @@ + + + + + + + + @@ -3287,6 +3540,14 @@ + + + + + + + + @@ -3303,6 +3564,14 @@ + + + + + + + + @@ -3319,6 +3588,14 @@ + + + + + + + + @@ -3343,6 +3620,14 @@ + + + + + + + + @@ -3359,6 +3644,14 @@ + + + + + + + + @@ -3375,11 +3668,24 @@ + + + + + + + + + + + + + @@ -3559,6 +3865,14 @@ + + + + + + + + @@ -3567,11 +3881,24 @@ + + + + + + + + + + + + + @@ -3624,5 +3951,13 @@ + + + + + + + + diff --git a/settings.gradle b/settings.gradle index 68b0cbfbc4..cadfe73422 100644 --- a/settings.gradle +++ b/settings.gradle @@ -36,6 +36,6 @@ include 'statsnode' include 'apitest' include 'platform' include 'code-coverage-report' -include 'bitcoind' +includeBuild 'bitcoind' rootProject.name = 'bisq' From abcc9f8185a850915b2d1e4c08f0d01c8d3f84b4 Mon Sep 17 00:00:00 2001 From: Alva Swanson Date: Mon, 11 Nov 2024 03:29:14 +0000 Subject: [PATCH 2/2] core: Implement sendBsqTest First, the sendBsq test creates one BTC and two BSQ wallets. Afterward, it funds the BTC and one BSQ wallet with 1 BTC. Next, the funded BSQ wallet sends 100 BSQ to the second BSQ wallet. --- core/build.gradle | 8 + .../java/bisq/core/BitcoinjBsqTests.java | 148 ++++++++++++++++++ .../btc/wallet/BisqDefaultCoinSelector.java | 2 +- .../bisq/core/btc/wallet/BtcCoinSelector.java | 2 +- 4 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 core/src/integrationTest/java/bisq/core/BitcoinjBsqTests.java diff --git a/core/build.gradle b/core/build.gradle index b28c4c9aeb..09caf47638 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -68,6 +68,14 @@ dependencies { integrationTestImplementation('bitcoind:regtest') { exclude(module: 'kotlin-stdlib-jdk8') } + integrationTestImplementation libs.hamcrest + integrationTestImplementation libs.mockito.core + integrationTestImplementation libs.mockito.junit.jupiter + + integrationTestImplementation libs.junit.jupiter.api + integrationTestImplementation libs.junit.jupiter.params + + integrationTestRuntimeOnly libs.junit.jupiter.engine } test { diff --git a/core/src/integrationTest/java/bisq/core/BitcoinjBsqTests.java b/core/src/integrationTest/java/bisq/core/BitcoinjBsqTests.java new file mode 100644 index 0000000000..ae35da4b10 --- /dev/null +++ b/core/src/integrationTest/java/bisq/core/BitcoinjBsqTests.java @@ -0,0 +1,148 @@ +package bisq.core; + +import bisq.core.btc.exceptions.BsqChangeBelowDustException; +import bisq.core.btc.wallet.BisqDefaultCoinSelector; +import bisq.core.btc.wallet.BsqCoinSelector; +import bisq.core.btc.wallet.BsqWalletV2; +import bisq.core.btc.wallet.BtcWalletV2; +import bisq.core.btc.wallet.WalletFactory; +import bisq.core.dao.state.DaoStateService; +import bisq.core.dao.state.model.blockchain.TxOutputKey; +import bisq.core.dao.state.unconfirmed.UnconfirmedBsqChangeOutputListService; + +import org.bitcoinj.core.Address; +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.InsufficientMoneyException; +import org.bitcoinj.core.TransactionOutput; +import org.bitcoinj.kits.WalletAppKit; +import org.bitcoinj.params.RegTestParams; +import org.bitcoinj.wallet.Wallet; + +import java.nio.file.Path; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + + + +import bisq.wallets.regtest.BitcoindExtension; +import bisq.wallets.regtest.bitcoind.BitcoindRegtestSetup; + +@ExtendWith(BitcoindExtension.class) +@Slf4j +public class BitcoinjBsqTests { + + private static class BisqRegtestNetworkParams extends RegTestParams { + public void setPort(int port) { + this.port = port; + } + } + + private final BitcoindRegtestSetup regtestSetup; + private final BisqRegtestNetworkParams networkParams; + + public BitcoinjBsqTests(BitcoindRegtestSetup regtestSetup) { + this.regtestSetup = regtestSetup; + networkParams = new BisqRegtestNetworkParams(); + networkParams.setPort(regtestSetup.getP2pPort()); + } + + @Test + void sendBsqTest(@TempDir Path tempDir) throws InterruptedException, InsufficientMoneyException, BsqChangeBelowDustException { + var walletFactory = new WalletFactory(networkParams); + Wallet btcWallet = walletFactory.createBtcWallet(); + Wallet secondBsqWallet = walletFactory.createBsqWallet(); + + var wallets = List.of(btcWallet, secondBsqWallet); + var regtestWalletAppKit = new RegtestWalletAppKit(networkParams, tempDir, wallets); + regtestWalletAppKit.initialize(); + + WalletAppKit walletAppKit = regtestWalletAppKit.getWalletAppKit(); + Wallet bsqWallet = walletAppKit.wallet(); + + var bsqWalletReceivedLatch = new CountDownLatch(1); + bsqWallet.addCoinsReceivedEventListener((wallet, tx, prevBalance, newBalance) -> + bsqWalletReceivedLatch.countDown()); + + var btcWalletReceivedLatch = new CountDownLatch(1); + btcWallet.addCoinsReceivedEventListener((wallet, tx, prevBalance, newBalance) -> + btcWalletReceivedLatch.countDown()); + + Address currentReceiveAddress = bsqWallet.currentReceiveAddress(); + String address = currentReceiveAddress.toString(); + regtestSetup.fundAddress(address, 1.0); + + currentReceiveAddress = btcWallet.currentReceiveAddress(); + address = currentReceiveAddress.toString(); + regtestSetup.fundAddress(address, 1.0); + + regtestSetup.mineOneBlock(); + + boolean isSuccess = bsqWalletReceivedLatch.await(30, TimeUnit.SECONDS); + assertThat("BSQ wallet not funded after 30 seconds.", isSuccess); + + Coin balance = bsqWallet.getBalance(); + assertThat("BitcoinJ BSQ wallet balance should equal 1 BTC.", balance.equals(Coin.COIN)); + + isSuccess = btcWalletReceivedLatch.await(30, TimeUnit.SECONDS); + assertThat("BTC wallet not funded after 30 seconds.", isSuccess); + + balance = btcWallet.getBalance(); + assertThat("BitcoinJ BTC wallet balance should equal 1 BTC.", balance.equals(Coin.COIN)); + + DaoStateService daoStateService = mock(DaoStateService.class); + doReturn(true).when(daoStateService) + .isTxOutputSpendable(any(TxOutputKey.class)); + + var bsqCoinSelector = new BsqCoinSelector(daoStateService, mock(UnconfirmedBsqChangeOutputListService.class)); + var btcCoinSelector = new BisqDefaultCoinSelector(true) { + @Override + protected boolean isDustAttackUtxo(TransactionOutput output) { + return false; + } + + @Override + protected boolean isTxOutputSpendable(TransactionOutput output) { + return true; + } + }; + + var btcWalletV2 = new BtcWalletV2(btcCoinSelector, btcWallet); + var bsqWalletV2 = new BsqWalletV2(networkParams, + walletAppKit.peerGroup(), + btcWalletV2, + bsqWallet, + bsqCoinSelector); + + var secondBsqWalletReceivedLatch = new CountDownLatch(1); + secondBsqWallet.addCoinsReceivedEventListener((wallet, tx, prevBalance, newBalance) -> + secondBsqWalletReceivedLatch.countDown()); + + // Send 100 BSQ (1 BSQ = 100 Satoshis) + Address receiverAddress = secondBsqWallet.currentReceiveAddress(); + Coin receiverAmount = Coin.ofSat(100 * 100); + bsqWalletV2.sendBsq(receiverAddress, receiverAmount, Coin.ofSat(10)); + + regtestSetup.mineOneBlock(); + + isSuccess = secondBsqWalletReceivedLatch.await(30, TimeUnit.SECONDS); + assertThat("Didn't receive BSQ after 30 seconds.", isSuccess); + + assertEquals(bsqWallet.getBalance(), Coin.ofSat(99990000)); + assertEquals(btcWallet.getBalance(), Coin.ofSat(99999747)); + assertEquals(secondBsqWallet.getBalance(), Coin.ofSat(10000)); + } +} diff --git a/core/src/main/java/bisq/core/btc/wallet/BisqDefaultCoinSelector.java b/core/src/main/java/bisq/core/btc/wallet/BisqDefaultCoinSelector.java index f92e497086..932932280b 100644 --- a/core/src/main/java/bisq/core/btc/wallet/BisqDefaultCoinSelector.java +++ b/core/src/main/java/bisq/core/btc/wallet/BisqDefaultCoinSelector.java @@ -134,7 +134,7 @@ protected boolean isTxSpendable(Transaction tx) { return isConfirmed || (isPending && (permitForeignPendingTx || isOwnTx)); } - abstract boolean isTxOutputSpendable(TransactionOutput output); + protected abstract boolean isTxOutputSpendable(TransactionOutput output); // TODO Why it uses coin age and not try to minimize number of inputs as the highest priority? // Asked Oscar and he also don't knows why coin age is used. Should be changed so that min. number of inputs is diff --git a/core/src/main/java/bisq/core/btc/wallet/BtcCoinSelector.java b/core/src/main/java/bisq/core/btc/wallet/BtcCoinSelector.java index 91b0c84c57..3d15fbad41 100644 --- a/core/src/main/java/bisq/core/btc/wallet/BtcCoinSelector.java +++ b/core/src/main/java/bisq/core/btc/wallet/BtcCoinSelector.java @@ -31,7 +31,7 @@ * We lookup for spendable outputs which matches any of our addresses. */ @Slf4j -class BtcCoinSelector extends BisqDefaultCoinSelector { +public class BtcCoinSelector extends BisqDefaultCoinSelector { private final Set
addresses; private final long ignoreDustThreshold;