diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1c29bec9432..44dbd92fcb5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,6 +26,28 @@ jobs: distribution: 'zulu' - name: Build with Gradle - uses: gradle/gradle-build-action@v2.4.2 + uses: gradle/gradle-build-action@v2.5.1 with: - arguments: build --scan + arguments: build jacocoTestReport testCodeCoverageReport --scan + + - name: Upload coverage report + uses: actions/upload-artifact@v3 + with: + name: coverage-report + path: code-coverage-report/build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml + + publish_coverage: + name: Publish coverage + runs-on: ubuntu-latest + needs: build + steps: + - name: Download coverage report + uses: actions/download-artifact@v3 + with: + name: coverage-report + path: code-coverage-report/build/reports/jacoco/testCodeCoverageReport + - name: Run codacy-coverage-reporter + uses: codacy/codacy-coverage-reporter-action@v1.3.0 + with: + project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} + coverage-reports: ${{ github.workspace }}/code-coverage-report/build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml diff --git a/apitest/dao-setup.gradle b/apitest/dao-setup.gradle index 12b9e7188e8..16b1694c650 100644 --- a/apitest/dao-setup.gradle +++ b/apitest/dao-setup.gradle @@ -11,14 +11,14 @@ // // The :apitest subproject will not run on Windows, and these tasks have not been // tested on Windows. -def buildResourcesDir = project(":apitest").buildDir.path + '/resources/main' +def buildResourcesDir = project(":apitest").sourceSets.main.resources.srcDirs.first().path // This task requires ant in the system $PATH. task installDaoSetup(dependsOn: 'cleanDaoSetup') { doLast { println "Installing dao-setup directories in build dir $buildResourcesDir ..." def src = 'https://github.com/bisq-network/bisq/raw/master/docs/dao-setup.zip' - def destfile = project.rootDir.path + '/apitest/src/main/resources/dao-setup.zip' + def destfile = buildResourcesDir + '/dao-setup.zip' def url = new URL(src) def f = new File(destfile) if (f.exists()) { @@ -48,21 +48,14 @@ task installDaoSetup(dependsOn: 'cleanDaoSetup') { } // Copy files from unzip target dir 'dao-setup' to build/resources/main. - def daoSetupSrc = project.rootDir.path + '/apitest/src/main/resources/dao-setup' - def daoSetupDest = buildResourcesDir + '/dao-setup' + def daoSetupSrc = buildResourcesDir + '/dao-setup' + def daoSetupDest = buildResourcesDir println "Copying $daoSetupSrc to $daoSetupDest ..." copy { from daoSetupSrc into daoSetupDest } - // Move dao-setup files from build/resources/main/dao-setup to build/resources/main - file(buildResourcesDir + '/dao-setup/Bitcoin-regtest') - .renameTo(file(buildResourcesDir + '/Bitcoin-regtest')) - file(buildResourcesDir + '/dao-setup/bisq-BTC_REGTEST_Alice_dao') - .renameTo(file(buildResourcesDir + '/bisq-BTC_REGTEST_Alice_dao')) - file(buildResourcesDir + '/dao-setup/bisq-BTC_REGTEST_Bob_dao') - .renameTo(file(buildResourcesDir + '/bisq-BTC_REGTEST_Bob_dao')) delete file(buildResourcesDir + '/dao-setup') } } diff --git a/apitest/src/main/java/bisq/apitest/Scaffold.java b/apitest/src/main/java/bisq/apitest/Scaffold.java index 3ffb4ff3eb5..47cd1d42cf6 100644 --- a/apitest/src/main/java/bisq/apitest/Scaffold.java +++ b/apitest/src/main/java/bisq/apitest/Scaffold.java @@ -242,28 +242,37 @@ public void installDaoSetupDirectories() { log.info("Copied all dao-setup files to {}", buildDataDir); - // Try to avoid confusion about which 'bisq.properties' file is or was loaded - // by a Bisq instance: delete the 'bisq.properties' file automatically copied - // to the 'apitest/build/resources/main' directory during IDE or Gradle build. - // Note: there is no way to prevent this deleted file from being re-copied - // from 'src/main/resources' to the buildDataDir each time you hit the build - // button in the IDE. - BashCommand rmRedundantBisqPropertiesFile = - new BashCommand("rm -rf " + buildDataDir + "/bisq.properties"); - if (rmRedundantBisqPropertiesFile.run().getExitStatus() != 0) - throw new IllegalStateException("Could not delete redundant bisq.properties file"); + // Copy 'bisq.properties' file from 'src/main/resources' to each + // Bisq instance's DataDir + copyBisqPropertiesFileToAppDataDir(); // Copy the blocknotify script from the src resources dir to the build // resources dir. Users may want to edit comment out some lines when all // the default block notifcation ports being will not be used (to avoid // seeing rpc notifcation warnings in log files). installBitcoinBlocknotify(); - } catch (IOException | InterruptedException ex) { throw new IllegalStateException("Could not install dao-setup files from " + daoSetupDir, ex); } } + private void copyBisqPropertiesFileToAppDataDir() throws IOException, InterruptedException { + copyBisqPropertiesFileToAppDataDir(alicedaemon); + copyBisqPropertiesFileToAppDataDir(bobdaemon); + } + + private void copyBisqPropertiesFileToAppDataDir(BisqAppConfig bisqAppConfig) throws IOException, InterruptedException { + if (!new File(config.baseSrcResourcesDir + "/bisq.properties").exists()) return; + + String instanceDataDir = config.rootAppDataDir + "/" + bisqAppConfig.appName; + BashCommand moveToDataDir = + new BashCommand("cp " + config.baseSrcResourcesDir + "/bisq.properties " + instanceDataDir); + if (moveToDataDir.run().getExitStatus() != 0) + throw new IllegalStateException("Could not copy bisq.properties to " + instanceDataDir); + log.debug("bisq.properties copied to " + instanceDataDir); + } + + private void cleanDaoSetupDirectories() { String buildDataDir = config.rootAppDataDir.getAbsolutePath(); log.info("Cleaning dao-setup data in {}", buildDataDir); diff --git a/apitest/src/main/java/bisq/apitest/config/ApiTestConfig.java b/apitest/src/main/java/bisq/apitest/config/ApiTestConfig.java index ec2076e885e..1526e5d6698 100644 --- a/apitest/src/main/java/bisq/apitest/config/ApiTestConfig.java +++ b/apitest/src/main/java/bisq/apitest/config/ApiTestConfig.java @@ -85,6 +85,8 @@ public class ApiTestConfig { static final String ENABLE_BISQ_DEBUGGING = "enableBisqDebugging"; static final String REGISTER_DISPUTE_AGENTS = "registerDisputeAgents"; + static final String SUSPEND_INSTANCE = "suspendInstance"; + // Default values for certain options static final String DEFAULT_CONFIG_FILE_NAME = "apitest.properties"; @@ -126,6 +128,8 @@ public class ApiTestConfig { public final String baseBuildResourcesDir; public final String baseSrcResourcesDir; + public final String suspendedInstances; + // The parser that will be used to parse both cmd line and config file options private final OptionParser parser = new OptionParser(); @@ -261,6 +265,12 @@ public ApiTestConfig(String... args) { .withRequiredArg() .ofType(Boolean.class) .defaultsTo(true); + + ArgumentAcceptingOptionSpec suspendInstancesOpt = + parser.accepts(SUSPEND_INSTANCE, "Suspended the given instances when debugging") + .withRequiredArg() + .ofType(String.class) + .defaultsTo(EMPTY); try { CompositeOptionSet options = new CompositeOptionSet(); @@ -319,6 +329,7 @@ public ApiTestConfig(String... args) { this.callRateMeteringConfigPath = options.valueOf(callRateMeteringConfigPathOpt); this.enableBisqDebugging = options.valueOf(enableBisqDebuggingOpt); this.registerDisputeAgents = options.valueOf(registerDisputeAgentsOpt); + this.suspendedInstances = options.valueOf(suspendInstancesOpt); // Assign values to special-case static fields. BASH_PATH_VALUE = bashPath; diff --git a/apitest/src/main/java/bisq/apitest/linux/BisqProcess.java b/apitest/src/main/java/bisq/apitest/linux/BisqProcess.java index a3a9374d0b5..971cd3742f4 100644 --- a/apitest/src/main/java/bisq/apitest/linux/BisqProcess.java +++ b/apitest/src/main/java/bisq/apitest/linux/BisqProcess.java @@ -23,6 +23,7 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import lombok.extern.slf4j.Slf4j; @@ -66,8 +67,9 @@ public BisqProcess(BisqAppConfig bisqAppConfig, ApiTestConfig config) { this.useDevPrivilegeKeys = true; this.findBisqPidScript = (config.isRunningTest ? "." : "./apitest") + "/scripts/get-bisq-pid.sh"; - this.debugOpts = config.enableBisqDebugging - ? " -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:" + bisqAppConfig.remoteDebugPort + this.debugOpts = config.enableBisqDebugging ? " -agentlib:jdwp=transport=dt_socket,server=y," + + "suspend=" + (Arrays.asList(config.suspendedInstances.split(",")).contains(bisqAppConfig.name()) ? "y" : "n") + + ",address=*:" + bisqAppConfig.remoteDebugPort : ""; } diff --git a/apitest/src/test/java/bisq/apitest/ApiTestCase.java b/apitest/src/test/java/bisq/apitest/ApiTestCase.java index 315561cbb6f..97732712f27 100644 --- a/apitest/src/test/java/bisq/apitest/ApiTestCase.java +++ b/apitest/src/test/java/bisq/apitest/ApiTestCase.java @@ -87,8 +87,7 @@ public static void setUpScaffold(Enum... supportingApps) throws InterruptedException, ExecutionException, IOException { String[] params = new String[]{ "--supportingApps", stream(supportingApps).map(Enum::name).collect(Collectors.joining(",")), - "--callRateMeteringConfigPath", getTestRateMeterInterceptorConfig().getAbsolutePath(), - "--enableBisqDebugging", "false" + "--callRateMeteringConfigPath", getTestRateMeterInterceptorConfig().getAbsolutePath() }; setUpScaffold(params); } diff --git a/apitest/src/test/java/bisq/apitest/method/CallRateMeteringInterceptorTest.java b/apitest/src/test/java/bisq/apitest/method/CallRateMeteringInterceptorTest.java index f7a076a9f96..e26ee2548c1 100644 --- a/apitest/src/test/java/bisq/apitest/method/CallRateMeteringInterceptorTest.java +++ b/apitest/src/test/java/bisq/apitest/method/CallRateMeteringInterceptorTest.java @@ -45,7 +45,6 @@ public class CallRateMeteringInterceptorTest extends MethodTest { @BeforeAll public static void setUp() { startSupportingApps(false, - false, bitcoind, alicedaemon); } diff --git a/apitest/src/test/java/bisq/apitest/method/MethodTest.java b/apitest/src/test/java/bisq/apitest/method/MethodTest.java index 8ac98eee751..a1afd5c99f7 100644 --- a/apitest/src/test/java/bisq/apitest/method/MethodTest.java +++ b/apitest/src/test/java/bisq/apitest/method/MethodTest.java @@ -70,13 +70,11 @@ public class MethodTest extends ApiTestCase { public static void startSupportingApps(File callRateMeteringConfigFile, boolean generateBtcBlock, - boolean startSupportingAppsInDebugMode, Enum... supportingApps) { try { setUpScaffold(new String[]{ "--supportingApps", toNameList.apply(supportingApps), "--callRateMeteringConfigPath", callRateMeteringConfigFile.getAbsolutePath(), - "--enableBisqDebugging", startSupportingAppsInDebugMode ? "true" : "false" }); doPostStartup(generateBtcBlock); } catch (Exception ex) { @@ -84,16 +82,13 @@ public static void startSupportingApps(File callRateMeteringConfigFile, } } - public static void startSupportingApps(boolean generateBtcBlock, - boolean startSupportingAppsInDebugMode, - Enum... supportingApps) { + public static void startSupportingApps(boolean generateBtcBlock, Enum... supportingApps) { try { // Disable call rate metering where there is no callRateMeteringConfigFile. File callRateMeteringConfigFile = getTestRateMeterInterceptorConfig(); setUpScaffold(new String[]{ "--supportingApps", toNameList.apply(supportingApps), - "--callRateMeteringConfigPath", callRateMeteringConfigFile.getAbsolutePath(), - "--enableBisqDebugging", startSupportingAppsInDebugMode ? "true" : "false" + "--callRateMeteringConfigPath", callRateMeteringConfigFile.getAbsolutePath() }); doPostStartup(generateBtcBlock); } catch (Exception ex) { diff --git a/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java index 627c1befe3b..2275d1b003b 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java @@ -77,12 +77,7 @@ public abstract class AbstractOfferTest extends MethodTest { @BeforeAll public static void setUp() { - setUp(false); - } - - public static void setUp(boolean startSupportingAppsInDebugMode) { startSupportingApps(true, - startSupportingAppsInDebugMode, bitcoind, seednode, arbdaemon, diff --git a/apitest/src/test/java/bisq/apitest/method/offer/BsqSwapOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/BsqSwapOfferTest.java index e1824f8c91a..81022eaf572 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/BsqSwapOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/BsqSwapOfferTest.java @@ -44,10 +44,6 @@ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class BsqSwapOfferTest extends AbstractOfferTest { - @BeforeAll - public static void setUp() { - AbstractOfferTest.setUp(false); - } @BeforeEach public void generateBtcBlock() { diff --git a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java index 4872c21032b..bc318f11ee5 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java @@ -19,8 +19,13 @@ import bisq.core.payment.PaymentAccount; +import bisq.proto.grpc.OfferInfo; + import io.grpc.StatusRuntimeException; +import java.util.List; +import java.util.concurrent.TimeoutException; + import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Disabled; @@ -59,8 +64,8 @@ public void testTakeAlicesBuyOffer(final TestInfo testInfo) { PaymentAccount alicesUsdAccount = createDummyF2FAccount(aliceClient, "US"); var alicesOffer = aliceClient.createMarketBasedPricedOffer(BUY.name(), USD, - 12_500_000L, - 12_500_000L, // min-amount = amount + 10_000_000L, + 10_000_000L, // min-amount = amount 0.00, defaultBuyerSecurityDepositPct.get(), alicesUsdAccount.getId(), @@ -71,19 +76,34 @@ public void testTakeAlicesBuyOffer(final TestInfo testInfo) { // Wait for Alice's AddToOfferBook task. // Wait times vary; my logs show >= 2-second delay. - sleep(3_000); // TODO loop instead of hard code a wait time - var alicesUsdOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), USD); + var timeout = System.currentTimeMillis() + 3000; + while (bobClient.getOffersSortedByDate(USD, true).size() < 1) { + sleep(100); + if (System.currentTimeMillis() > timeout) + fail(new TimeoutException("Timed out waiting for Offer to be added to OfferBook")); + } + + List alicesUsdOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), USD); assertEquals(1, alicesUsdOffers.size()); PaymentAccount bobsUsdAccount = createDummyF2FAccount(bobClient, "US"); var ignoredTakeOfferAmountParam = 0L; + var trade = takeAlicesOffer(offerId, bobsUsdAccount.getId(), TRADE_FEE_CURRENCY_CODE, ignoredTakeOfferAmountParam, false); - sleep(2_500); // Allow available offer to be removed from offer book. - alicesUsdOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), USD); + + // Allow available offer to be removed from offer book. + timeout = System.currentTimeMillis() + 2500; + do { + alicesUsdOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), USD); + sleep(100); + if (System.currentTimeMillis() > timeout) + fail(new TimeoutException("Timed out waiting for Offer to be removed from OfferBook")); + } while (alicesUsdOffers.size() > 0); + assertEquals(0, alicesUsdOffers.size()); trade = bobClient.getTrade(tradeId); diff --git a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferWithNationalBankAcctTest.java b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferWithNationalBankAcctTest.java index 9c1a1f99ebe..2d51648d963 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferWithNationalBankAcctTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferWithNationalBankAcctTest.java @@ -78,10 +78,6 @@ public class TakeBuyBTCOfferWithNationalBankAcctTest extends AbstractTradeTest { private static PaymentAccount alicesPaymentAccount; private static PaymentAccount bobsPaymentAccount; - @BeforeAll - public static void setUp() { - setUp(false); - } @Test @Order(1) diff --git a/apitest/src/test/java/bisq/apitest/method/trade/TakeSellXMROfferTest.java b/apitest/src/test/java/bisq/apitest/method/trade/TakeSellXMROfferTest.java index ad9162d09af..ef322d7e18c 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/TakeSellXMROfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeSellXMROfferTest.java @@ -60,7 +60,7 @@ public class TakeSellXMROfferTest extends AbstractTradeTest { @BeforeAll public static void setUp() { - AbstractOfferTest.setUp(false); + AbstractOfferTest.setUp(); createXmrPaymentAccounts(); EXPECTED_PROTOCOL_STATUS.init(); } diff --git a/apitest/src/test/java/bisq/apitest/method/wallet/BsqWalletTest.java b/apitest/src/test/java/bisq/apitest/method/wallet/BsqWalletTest.java index 010f0fe0d26..757bcd28443 100644 --- a/apitest/src/test/java/bisq/apitest/method/wallet/BsqWalletTest.java +++ b/apitest/src/test/java/bisq/apitest/method/wallet/BsqWalletTest.java @@ -48,7 +48,6 @@ public class BsqWalletTest extends MethodTest { @BeforeAll public static void setUp() { startSupportingApps(true, - false, bitcoind, seednode, arbdaemon, diff --git a/apitest/src/test/java/bisq/apitest/method/wallet/BtcTxFeeRateTest.java b/apitest/src/test/java/bisq/apitest/method/wallet/BtcTxFeeRateTest.java index 1925b4452c5..ff3861b5894 100644 --- a/apitest/src/test/java/bisq/apitest/method/wallet/BtcTxFeeRateTest.java +++ b/apitest/src/test/java/bisq/apitest/method/wallet/BtcTxFeeRateTest.java @@ -36,7 +36,6 @@ public class BtcTxFeeRateTest extends MethodTest { @BeforeAll public static void setUp() { startSupportingApps(false, - true, bitcoind, seednode, alicedaemon); diff --git a/apitest/src/test/java/bisq/apitest/method/wallet/BtcWalletTest.java b/apitest/src/test/java/bisq/apitest/method/wallet/BtcWalletTest.java index 59eeab906ab..00de34c0c08 100644 --- a/apitest/src/test/java/bisq/apitest/method/wallet/BtcWalletTest.java +++ b/apitest/src/test/java/bisq/apitest/method/wallet/BtcWalletTest.java @@ -41,7 +41,6 @@ public class BtcWalletTest extends MethodTest { @BeforeAll public static void setUp() { startSupportingApps(false, - false, bitcoind, seednode, alicedaemon, diff --git a/apitest/src/test/java/bisq/apitest/scenario/OfferTest.java b/apitest/src/test/java/bisq/apitest/scenario/OfferTest.java index 8141e909022..cfed6eae026 100644 --- a/apitest/src/test/java/bisq/apitest/scenario/OfferTest.java +++ b/apitest/src/test/java/bisq/apitest/scenario/OfferTest.java @@ -43,11 +43,6 @@ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class OfferTest extends AbstractOfferTest { - @BeforeAll - public static void setUp() { - setUp(false); // Use setUp(true) for running API daemons in remote debug mode. - } - @Test @Order(1) public void testCreateOfferValidation() { diff --git a/apitest/src/test/java/bisq/apitest/scenario/StartupTest.java b/apitest/src/test/java/bisq/apitest/scenario/StartupTest.java index 74df4f78a56..d0bb08087ae 100644 --- a/apitest/src/test/java/bisq/apitest/scenario/StartupTest.java +++ b/apitest/src/test/java/bisq/apitest/scenario/StartupTest.java @@ -57,7 +57,6 @@ public static void setUp() { try { callRateMeteringConfigFile = getTestRateMeterInterceptorConfig(); startSupportingApps(callRateMeteringConfigFile, - false, false, bitcoind, seednode, arbdaemon, alicedaemon); } catch (Exception ex) { diff --git a/apitest/src/test/java/bisq/apitest/scenario/WalletTest.java b/apitest/src/test/java/bisq/apitest/scenario/WalletTest.java index 17b88eca5bc..ae8fe053c5b 100644 --- a/apitest/src/test/java/bisq/apitest/scenario/WalletTest.java +++ b/apitest/src/test/java/bisq/apitest/scenario/WalletTest.java @@ -53,7 +53,6 @@ public class WalletTest extends MethodTest { @BeforeAll public static void setUp() { startSupportingApps(true, - false, bitcoind, seednode, arbdaemon, diff --git a/build-logic/commons/src/main/groovy/bisq.java-conventions.gradle b/build-logic/commons/src/main/groovy/bisq.java-conventions.gradle index 87449d028eb..08c9fb5d904 100644 --- a/build-logic/commons/src/main/groovy/bisq.java-conventions.gradle +++ b/build-logic/commons/src/main/groovy/bisq.java-conventions.gradle @@ -1,5 +1,6 @@ plugins { id 'java' + id 'jacoco' } repositories { @@ -10,11 +11,6 @@ repositories { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 -repositories { - mavenCentral() - maven { url 'https://jitpack.io' } -} - tasks.withType(JavaCompile) { options.encoding = 'UTF-8' } @@ -31,3 +27,10 @@ dependencies { test { useJUnitPlatform() } + +jacocoTestReport { + reports { + html.required = false + xml.required = true + } +} diff --git a/build.gradle b/build.gradle index 407c14ea8ea..1be40c69ac0 100644 --- a/build.gradle +++ b/build.gradle @@ -26,9 +26,7 @@ configure(subprojects) { apply plugin: 'com.google.osdetector' ext { // in alphabetical order - grpcVersion = '1.42.1' javafxVersion = '16' - protocVersion = '3.19.1' os = osdetector.os == 'osx' ? 'mac' : osdetector.os == 'windows' ? 'win' : osdetector.os } @@ -130,6 +128,7 @@ configure(project(':common')) { "Implementation-Version": getHash()) dependencies { + implementation enforcedPlatform(project(':platform')) implementation project(':proto') annotationProcessor libs.lombok compileOnly libs.javax.annotation @@ -142,7 +141,6 @@ configure(project(':common')) { exclude(module: 'jsr305') exclude(module: 'okhttp') exclude(module: 'okio') - exclude(module: 'protobuf-java') exclude(module: 'slf4j-api') } implementation libs.google.findbugs @@ -151,7 +149,6 @@ configure(project(':common')) { implementation(libs.google.guice) { exclude(module: 'guava') } - implementation libs.protobuf.java implementation libs.commons.io implementation libs.jopt implementation libs.apache.commons.lang3 @@ -176,12 +173,12 @@ configure(project(':p2p')) { } dependencies { + implementation enforcedPlatform(project(':platform')) implementation project(':proto') implementation project(':common') annotationProcessor libs.lombok compileOnly libs.lombok implementation libs.google.guava - implementation libs.protobuf.java implementation libs.fxmisc.easybind implementation libs.slf4j.api implementation(libs.netlayer.tor.external) { @@ -196,7 +193,6 @@ configure(project(':p2p')) { exclude(module: 'jsr305') exclude(module: 'okhttp') exclude(module: 'okio') - exclude(module: 'protobuf-java') exclude(module: 'slf4j-api') } implementation(libs.google.guice) { @@ -223,6 +219,7 @@ configure(project(':core')) { } dependencies { + implementation enforcedPlatform(project(':platform')) implementation project(':proto') implementation project(':assets') implementation project(':common') @@ -237,7 +234,6 @@ configure(project(':core')) { implementation libs.google.findbugs implementation libs.google.gson implementation libs.google.guava - implementation libs.protobuf.java implementation libs.commons.codec implementation libs.commons.io implementation libs.jopt @@ -261,7 +257,6 @@ configure(project(':core')) { exclude(module: 'jsr305') exclude(module: 'okhttp') exclude(module: 'okio') - exclude(module: 'protobuf-java') exclude(module: 'slf4j-api') } implementation(libs.jsonrpc4j) { @@ -313,6 +308,7 @@ configure(project(':desktop')) { sourceSets.main.resources.srcDirs += ['src/main/java'] // to copy fxml and css files dependencies { + implementation enforcedPlatform(project(':platform')) implementation project(':assets') implementation project(':common') implementation project(':proto') @@ -324,7 +320,6 @@ configure(project(':desktop')) { implementation libs.logback.core implementation libs.google.gson implementation libs.google.guava - implementation libs.protobuf.java implementation libs.jcsv implementation libs.jfoenix implementation libs.commons.io @@ -343,7 +338,6 @@ configure(project(':desktop')) { exclude(module: 'jsr305') exclude(module: 'okhttp') exclude(module: 'okio') - exclude(module: 'protobuf-java') exclude(module: 'slf4j-api') } implementation(libs.google.guice) { @@ -373,6 +367,7 @@ configure(project(':seednode')) { mainClassName = 'bisq.seednode.SeedNodeMain' dependencies { + implementation enforcedPlatform(project(':platform')) implementation project(':common') implementation project(':proto') implementation project(':p2p') @@ -465,6 +460,7 @@ configure(project(':apitest')) { } dependencies { + implementation enforcedPlatform(project(':platform')) implementation project(':proto') implementation project(':common') implementation project(':core') @@ -479,7 +475,6 @@ configure(project(':apitest')) { implementation libs.logback.core implementation libs.google.gson implementation libs.google.guava - implementation libs.protobuf.java implementation libs.jopt implementation libs.apache.commons.lang3 implementation libs.slf4j.api @@ -489,7 +484,6 @@ configure(project(':apitest')) { exclude(module: 'jsr305') exclude(module: 'okhttp') exclude(module: 'okio') - exclude(module: 'protobuf-java') exclude(module: 'slf4j-api') } implementation(libs.grpc.protobuf) { diff --git a/code-coverage-report/build.gradle b/code-coverage-report/build.gradle new file mode 100644 index 00000000000..5bb79dba625 --- /dev/null +++ b/code-coverage-report/build.gradle @@ -0,0 +1,26 @@ +plugins { + id 'base' + id 'jacoco-report-aggregation' +} + +repositories { + mavenCentral() + maven { url "https://jitpack.io" } +} + +dependencies { + jacocoAggregation project(':apitest') + jacocoAggregation project(':cli') + jacocoAggregation project(':daemon') + jacocoAggregation project(':desktop') + jacocoAggregation project(':seednode') + jacocoAggregation project(':statsnode') +} + +reporting { + reports { + testCodeCoverageReport(JacocoCoverageReport) { + testType = TestSuiteType.UNIT_TEST + } + } +} diff --git a/core/src/main/java/bisq/core/dao/burningman/BurningManPresentationService.java b/core/src/main/java/bisq/core/dao/burningman/BurningManPresentationService.java index d32843536cf..b78fce15aa9 100644 --- a/core/src/main/java/bisq/core/dao/burningman/BurningManPresentationService.java +++ b/core/src/main/java/bisq/core/dao/burningman/BurningManPresentationService.java @@ -45,12 +45,14 @@ import com.google.common.annotations.VisibleForTesting; import java.util.Collection; +import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.LongStream; import lombok.extern.slf4j.Slf4j; @@ -359,6 +361,17 @@ public long getTotalAmountOfBurnedBsq() { .sum(); } + public long getBsqBurnedByMonth(Date dateFilter) { + Set defaultZeroBurn = new HashSet<>(); + defaultZeroBurn.add(new BurnOutputModel(0, 0, 0, "", 0L, 0)); + Map burningMen = getBurningManCandidatesByName(); + return burningMen.values().stream() + .map(burningMan -> burningMan.getBurnOutputModelsByMonth().getOrDefault(dateFilter, defaultZeroBurn).stream() + .mapToLong(BurnOutputModel::getAmount)) + .mapToLong(LongStream::sum) + .sum(); + } + public String getGenesisTxId() { return daoStateService.getGenesisTxId(); } diff --git a/core/src/main/java/bisq/core/dao/burningman/accounting/BurningManAccountingService.java b/core/src/main/java/bisq/core/dao/burningman/accounting/BurningManAccountingService.java index 3605eb981de..bf5138e91fc 100644 --- a/core/src/main/java/bisq/core/dao/burningman/accounting/BurningManAccountingService.java +++ b/core/src/main/java/bisq/core/dao/burningman/accounting/BurningManAccountingService.java @@ -271,6 +271,15 @@ public Stream getDistributedBtcBalanceByMonth(Date mont } + public void resyncAccountingDataFromScratch(Runnable resultHandler) { + burningManAccountingStoreService.removeAllBlocks(resultHandler); + } + + public void resyncAccountingDataFromResources() { + burningManAccountingStoreService.deleteStorageFile(); + } + + /////////////////////////////////////////////////////////////////////////////////////////// // Delegates /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/dao/burningman/accounting/node/AccountingNodeProvider.java b/core/src/main/java/bisq/core/dao/burningman/accounting/node/AccountingNodeProvider.java index b138067e762..cdfde45a6e8 100644 --- a/core/src/main/java/bisq/core/dao/burningman/accounting/node/AccountingNodeProvider.java +++ b/core/src/main/java/bisq/core/dao/burningman/accounting/node/AccountingNodeProvider.java @@ -38,15 +38,16 @@ public class AccountingNodeProvider { @Inject public AccountingNodeProvider(AccountingLiteNode liteNode, AccountingFullNode fullNode, - @Named(Config.IS_BM_FULL_NODE) boolean isBmFullNode, + @Named(Config.IS_BM_FULL_NODE) boolean isBmFullNodeFromOptions, Preferences preferences) { - boolean rpcDataSet = preferences.getRpcUser() != null && - !preferences.getRpcUser().isEmpty() - && preferences.getRpcPw() != null && - !preferences.getRpcPw().isEmpty() && + String rpcUser = preferences.getRpcUser(); + String rpcPw = preferences.getRpcPw(); + boolean rpcDataSet = rpcUser != null && !rpcUser.isEmpty() && + rpcPw != null && !rpcPw.isEmpty() && preferences.getBlockNotifyPort() > 0; - if (isBmFullNode && rpcDataSet) { + boolean fullBMAccountingNode = preferences.isFullBMAccountingNode(); + if ((fullBMAccountingNode || isBmFullNodeFromOptions) && rpcDataSet) { accountingNode = fullNode; } else { accountingNode = liteNode; diff --git a/core/src/main/java/bisq/core/dao/burningman/accounting/storage/BurningManAccountingStore.java b/core/src/main/java/bisq/core/dao/burningman/accounting/storage/BurningManAccountingStore.java index 50b456cbe40..cc80331f926 100644 --- a/core/src/main/java/bisq/core/dao/burningman/accounting/storage/BurningManAccountingStore.java +++ b/core/src/main/java/bisq/core/dao/burningman/accounting/storage/BurningManAccountingStore.java @@ -81,6 +81,16 @@ public void purgeLastTenBlocks() { } } + public void removeAllBlocks() { + Lock writeLock = readWriteLock.writeLock(); + writeLock.lock(); + try { + blocks.clear(); + } finally { + writeLock.unlock(); + } + } + public Optional getLastBlock() { Lock readLock = readWriteLock.readLock(); readLock.lock(); diff --git a/core/src/main/java/bisq/core/dao/burningman/accounting/storage/BurningManAccountingStoreService.java b/core/src/main/java/bisq/core/dao/burningman/accounting/storage/BurningManAccountingStoreService.java index 89c23cc7eef..4923ddd9160 100644 --- a/core/src/main/java/bisq/core/dao/burningman/accounting/storage/BurningManAccountingStoreService.java +++ b/core/src/main/java/bisq/core/dao/burningman/accounting/storage/BurningManAccountingStoreService.java @@ -49,6 +49,7 @@ @Singleton public class BurningManAccountingStoreService extends StoreService { private static final String FILE_NAME = "BurningManAccountingStore_v3"; + private volatile boolean removeAllBlocksCalled; @Inject public BurningManAccountingStoreService(ResourceDataStoreService resourceDataStoreService, @@ -83,6 +84,9 @@ public void requestPersistence() { } public void addIfNewBlock(AccountingBlock block) throws BlockHashNotConnectingException, BlockHeightNotConnectingException { + if (removeAllBlocksCalled) { + return; + } store.addIfNewBlock(block); requestPersistence(); } @@ -92,10 +96,27 @@ public void forEachBlock(Consumer consumer) { } public void purgeLastTenBlocks() { + if (removeAllBlocksCalled) { + return; + } store.purgeLastTenBlocks(); requestPersistence(); } + public void removeAllBlocks(Runnable resultHandler) { + removeAllBlocksCalled = true; + store.removeAllBlocks(); + persistenceManager.persistNow(resultHandler); + } + + public void deleteStorageFile() { + try { + FileUtil.deleteFileIfExists(Path.of(absolutePathOfStorageDir, FILE_NAME).toFile()); + } catch (IOException e) { + e.printStackTrace(); + } + } + public Optional getLastBlock() { return store.getLastBlock(); } diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index d3630dacc74..3da0cdd85d9 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -772,7 +772,7 @@ public void setTakeOfferSelectedPaymentAccountId(String value) { public void setDaoFullNode(boolean value) { // We only persist if we have not set the program argument - if (config.fullDaoNodeOptionSetExplicitly) { + if (!config.fullDaoNodeOptionSetExplicitly) { prefPayload.setDaoFullNode(value); requestPersistence(); } @@ -853,6 +853,11 @@ public void setProcessBurningManAccountingData(boolean processBurningManAccounti requestPersistence(); } + public void setFullBMAccountingNode(boolean isFullBMAccountingNode) { + prefPayload.setFullBMAccountingNode(isFullBMAccountingNode); + requestPersistence(); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Getter @@ -1023,6 +1028,10 @@ public boolean isProcessBurningManAccountingData() { return fullAccountingNodeFromOptions || prefPayload.isProcessBurningManAccountingData(); } + public boolean isFullBMAccountingNode() { + return prefPayload.isFullBMAccountingNode(); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Private @@ -1145,6 +1154,8 @@ private interface ExcludesDelegateMethods { boolean isProcessBurningManAccountingData(); + boolean isFullBMAccountingNode(); + void setDaoFullNode(boolean value); void setRpcUser(String value); @@ -1190,5 +1201,7 @@ private interface ExcludesDelegateMethods { void setUserHasRaisedTradeLimit(boolean userHasRaisedTradeLimit); void setProcessBurningManAccountingData(boolean processBurningManAccountingData); + + void setFullBMAccountingNode(boolean isFullBMAccountingNode); } } diff --git a/core/src/main/java/bisq/core/user/PreferencesPayload.java b/core/src/main/java/bisq/core/user/PreferencesPayload.java index eb74be1b6ef..761f9c89344 100644 --- a/core/src/main/java/bisq/core/user/PreferencesPayload.java +++ b/core/src/main/java/bisq/core/user/PreferencesPayload.java @@ -148,6 +148,10 @@ public final class PreferencesPayload implements PersistableEnvelope { // Added at 1.9.11 private boolean processBurningManAccountingData = false; + // Added at 1.9.11 + private boolean isFullBMAccountingNode = false; + + /////////////////////////////////////////////////////////////////////////////////////////// // Constructor /////////////////////////////////////////////////////////////////////////////////////////// @@ -220,7 +224,8 @@ public Message toProtoMessage() { .setUseBitcoinUrisInQrCodes(useBitcoinUrisInQrCodes) .setUserDefinedTradeLimit(userDefinedTradeLimit) .setUserHasRaisedTradeLimit(userHasRaisedTradeLimit) - .setProcessBurningManAccountingData(processBurningManAccountingData); + .setProcessBurningManAccountingData(processBurningManAccountingData) + .setIsFullBMAccountingNode(isFullBMAccountingNode); Optional.ofNullable(backupDirectory).ifPresent(builder::setBackupDirectory); Optional.ofNullable(preferredTradeCurrency).ifPresent(e -> builder.setPreferredTradeCurrency((protobuf.TradeCurrency) e.toProtoMessage())); @@ -328,7 +333,8 @@ public static PreferencesPayload fromProto(protobuf.PreferencesPayload proto, Co proto.getUseBitcoinUrisInQrCodes(), proto.getUserHasRaisedTradeLimit() ? proto.getUserDefinedTradeLimit() : Preferences.INITIAL_TRADE_LIMIT, proto.getUserHasRaisedTradeLimit(), - proto.getProcessBurningManAccountingData() + proto.getProcessBurningManAccountingData(), + proto.getIsFullBMAccountingNode() ); } } diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index a2e5bb9393b..abfc4a6e876 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -1427,6 +1427,28 @@ setting.preferences.resetAllFlags=Reset all \"Don't show again\" flags settings.preferences.languageChange=To apply the language change to all screens requires a restart. settings.preferences.supportLanguageWarning=In case of a dispute, please note that mediation is handled in {0} and arbitration in {1}. setting.preferences.daoOptions=DAO options + +setting.preferences.dao.resyncBMAccFromScratch=Resync from scratch +setting.preferences.dao.resyncBMAccFromScratch.popup=To resync the Burningmen accounting data from scratch you need to run a full Bitcoin node.\n\ + It will take considerable time and CPU resources as it resyncs from 2020 when legacy BM started. Depending on your CPU resources that can take 20-40 hours.\n\n\ + Are you sure you want to do that?\n\n\ + Mostly a resync from latest resource files is sufficient and much faster.\n\n\ + If you proceed, after an application restart the Burningmen accounting data will be rebuild from the transactions requested from your Bitcoin node. +setting.preferences.dao.resyncBMAccFromScratch.resync=Resync from scratch and shutdown +setting.preferences.dao.isFullBMAccountingNode=Full BM accounting node +setting.preferences.dao.resyncBMAccFromResources=Resync from resources +setting.preferences.dao.resyncBMAccFromResources.popup=To resync the Burningmen accounting data from resources you \ + will reapply the data from resources of your last downloaded Bisq version and load the missing data from the network.\n\ + Depending on the age of your last Bisq download that might take a while.\n\n\ + Are you sure you want to do that? +setting.preferences.dao.resyncBMAccFromResources.resync=Resync from resources and shutdown + +setting.preferences.dao.useFullBMAccountingNode.noDaoMode.popup=To run as full Burningmen accounting node you need to run as full DAO node as well.\n\n\ + Please enable first the Full-DAO mode option and config RPC. +setting.preferences.dao.useFullBMAccountingNode.enabledDaoMode.popup=To run as full Burningmen accounting node can take considerable time and CPU resources.\n\n\ + A restart is required to enable that change.\n\n\ + Do you want to enable full Burningmen accounting mode?. + setting.preferences.dao.resyncFromGenesis.label=Rebuild DAO state from genesis tx setting.preferences.dao.resyncFromResources.label=Rebuild DAO state from resources setting.preferences.dao.resyncFromResources.popup=After an application restart the Bisq network governance data will be reloaded from \ diff --git a/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/burningman/BurningManView.java b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/burningman/BurningManView.java index dd2acd47eac..4bbfdefcfac 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/burningman/BurningManView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/burningman/BurningManView.java @@ -675,7 +675,7 @@ private void onUpdateAvailableBalance(Coin availableBalance) { private void exportToCSV() { List result = new ArrayList<>(); String separator = "~"; - String tableColumns = "Month~BTC Fees~Fees as BSQ~DPT as BTC~DPT as BSQ~Distributed BTC~Distributed BTC as BSQ"; + String tableColumns = "Month~BTC Fees~Fees as BSQ~DPT as BTC~DPT as BSQ~Distributed BTC~Distributed BTC as BSQ~BSQ burned"; CSVEntryConverter headerConverter = item -> tableColumns.split(separator); CSVEntryConverter contentConverter = item -> item.split(separator); Date now = new Date(); @@ -686,6 +686,7 @@ private void exportToCSV() { long feeAsBsqSum = 0; long dptAsBsqSum = 0; long distributedBtcAsBsqSum = 0; + long bsqBurnedSum = 0; long feeAsBsq, dptAsBsq, distributedBtcAsBsq; int year = 2023; int month = 0; @@ -697,7 +698,7 @@ private void exportToCSV() { Map averageBsqPriceByMonth = burningManAccountingService.getAverageBsqPriceByMonth(); Optional price = Optional.ofNullable(averageBsqPriceByMonth.get(date)); - + long bsqBurned = burningManPresentationService.getBsqBurnedByMonth(date); List distributedBtcBalanceByMonth = burningManAccountingService.getDistributedBtcBalanceByMonth(date) .collect(Collectors.toList()); long feeAsBtc = distributedBtcBalanceByMonth.stream() @@ -726,7 +727,7 @@ private void exportToCSV() { feeAsBsqSum += feeAsBsq; dptAsBsqSum += dptAsBsq; distributedBtcAsBsqSum += distributedBtcAsBsq; - + bsqBurnedSum += bsqBurned; line = new SimpleDateFormat("MMM yyyy", Locale.ENGLISH).format(date) + separator + btcFormatter.formatCoin(Coin.valueOf(feeAsBtc)) @@ -734,7 +735,8 @@ private void exportToCSV() { + separator + btcFormatter.formatCoin(Coin.valueOf(dptAsBtc)) + separator + bsqFormatter.formatCoin(Coin.valueOf(dptAsBsq)) + separator + btcFormatter.formatCoin(Coin.valueOf(distributedBtc)) - + separator + bsqFormatter.formatCoin(Coin.valueOf(distributedBtcAsBsq)); + + separator + bsqFormatter.formatCoin(Coin.valueOf(distributedBtcAsBsq)) + + separator + bsqFormatter.formatCoin(Coin.valueOf(bsqBurned)); result.add(line); if (++month > 11) { month = 0; @@ -750,7 +752,8 @@ private void exportToCSV() { + separator + btcFormatter.formatCoin(Coin.valueOf(dptAsBtcSum)) + separator + bsqFormatter.formatCoin(Coin.valueOf(dptAsBsqSum)) + separator + btcFormatter.formatCoin(Coin.valueOf(distributedBtcSum)) - + separator + bsqFormatter.formatCoin(Coin.valueOf(distributedBtcAsBsqSum)); + + separator + bsqFormatter.formatCoin(Coin.valueOf(distributedBtcAsBsqSum)) + + separator + bsqFormatter.formatCoin(Coin.valueOf(bsqBurnedSum)); result.add(line); GUIUtil.exportCSV("Burningman_dao_revenue.csv", headerConverter, contentConverter, diff --git a/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/proofofburn/ProofOfBurnView.java b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/proofofburn/ProofOfBurnView.java index dc506d25386..034495b8a94 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/proofofburn/ProofOfBurnView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/proofofburn/ProofOfBurnView.java @@ -51,10 +51,15 @@ import org.bitcoinj.core.InsufficientMoneyException; import org.bitcoinj.core.Transaction; +import com.googlecode.jcsv.writer.CSVEntryConverter; + import javax.inject.Inject; import javax.inject.Named; +import javafx.stage.Stage; + import javafx.scene.control.Button; +import javafx.scene.control.Hyperlink; import javafx.scene.control.TableCell; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; @@ -62,6 +67,9 @@ import javafx.scene.control.Tooltip; import javafx.scene.layout.GridPane; +import javafx.geometry.HPos; +import javafx.geometry.Insets; + import javafx.beans.InvalidationListener; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.value.ChangeListener; @@ -72,7 +80,10 @@ import javafx.util.Callback; + +import java.util.ArrayList; import java.util.Comparator; +import java.util.List; import java.util.stream.Collectors; import static bisq.desktop.util.FormBuilder.addButtonAfterGroup; @@ -95,6 +106,7 @@ public class ProofOfBurnView extends ActivatableView implements private Button burnButton; private TableView myItemsTableView; private TableView allTxsTableView; + private Hyperlink exportAsCSVHyperlink; private final ObservableList myItemsObservableList = FXCollections.observableArrayList(); private final SortedList myItemsSortedList = new SortedList<>(myItemsObservableList); @@ -153,6 +165,14 @@ public void initialize() { createColumnsForAllTxs(); allTxsTableView.setItems(allItemsSortedList); + exportAsCSVHyperlink = new Hyperlink(Res.get("shared.exportCSV")); + GridPane.setRowIndex(exportAsCSVHyperlink, ++gridRow); + GridPane.setColumnIndex(exportAsCSVHyperlink, 0); + GridPane.setMargin(exportAsCSVHyperlink, new Insets(-5, 0, 0, 0)); + GridPane.setHalignment(exportAsCSVHyperlink, HPos.RIGHT); + root.getChildren().add(exportAsCSVHyperlink); + exportAsCSVHyperlink.setOnAction(e -> exportToCSV()); + createListeners(); } @@ -301,6 +321,27 @@ private void doPublishFeeTx(Transaction transaction, String preImageAsString) { preImageTextField.resetValidation(); } + private void exportToCSV() { + List result = new ArrayList<>(); + String separator = "~"; + String tableColumns = "Amount~Date~Hash~TxId~Pubkey"; + CSVEntryConverter headerConverter = item -> tableColumns.split(separator); + CSVEntryConverter contentConverter = item -> item.split(separator); + for (ProofOfBurnListItem item: allItemsSortedList) { + String line = bsqFormatter.formatCoin(item.getAmount()) + + separator + item.getDateAsString() + + separator + item.getHashAsHex() + + separator + item.getTxId() + + separator + item.getPubKey(); + result.add(line); + } + GUIUtil.exportCSV("proofOfBurnTransactions.csv", + headerConverter, + contentConverter, + "", + result, + (Stage) root.getScene().getWindow()); + } /////////////////////////////////////////////////////////////////////////////////////////// // Table columns diff --git a/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java b/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java index 1934b1cc5a2..ab2f519d970 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java @@ -24,7 +24,6 @@ import bisq.desktop.components.ExternalHyperlink; import bisq.desktop.components.HyperlinkWithIcon; import bisq.desktop.components.InputTextField; -import bisq.desktop.components.TitledGroupBg; import bisq.desktop.components.list.FilterBox; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.main.overlays.windows.TxDetails; @@ -53,6 +52,7 @@ import bisq.network.p2p.P2PService; import bisq.common.UserThread; +import bisq.common.util.Tuple2; import bisq.common.util.Tuple3; import bisq.common.util.Tuple4; @@ -130,7 +130,8 @@ public class WithdrawalView extends ActivatableView { private RadioButton useAllInputsRadioButton, useCustomInputsRadioButton, feeExcludedRadioButton, feeIncludedRadioButton; private Label amountLabel; - private TextField amountTextField, withdrawFromTextField, withdrawToTextField, withdrawMemoTextField, transactionFeeInputTextField; + private TextField amountTextField, withdrawToTextField, withdrawMemoTextField, transactionFeeInputTextField; + private String withdrawFromAddresses = ""; private final BtcWalletService btcWalletService; private final TradeManager tradeManager; @@ -188,8 +189,6 @@ private WithdrawalView(BtcWalletService btcWalletService, @Override public void initialize() { filterBox.initialize(filteredList, tableView); - final TitledGroupBg titledGroupBg = addTitledGroupBg(gridPane, rowIndex, 4, Res.get("funds.deposit.withdrawFromWallet")); - titledGroupBg.getStyleClass().add("last"); inputsToggleGroup = new ToggleGroup(); inputsToggleGroupListener = (observable, oldValue, newValue) -> { @@ -202,7 +201,7 @@ public void initialize() { Res.get("funds.withdrawal.inputs"), Res.get("funds.withdrawal.useAllInputs"), Res.get("funds.withdrawal.useCustomInputs"), - Layout.FIRST_ROW_DISTANCE); + 0); useAllInputsRadioButton = labelRadioButtonRadioButtonTuple3.second; useCustomInputsRadioButton = labelRadioButtonRadioButtonTuple3.third; @@ -222,14 +221,13 @@ public void initialize() { feeExcludedRadioButton = feeTuple3.third; feeIncludedRadioButton = feeTuple3.fourth; - withdrawFromTextField = addTopLabelTextField(gridPane, ++rowIndex, - Res.get("funds.withdrawal.fromLabel", Res.getBaseCurrencyCode())).second; - - withdrawToTextField = addTopLabelInputTextField(gridPane, ++rowIndex, - Res.get("funds.withdrawal.toLabel", Res.getBaseCurrencyCode())).second; - - withdrawMemoTextField = addTopLabelInputTextField(gridPane, ++rowIndex, - Res.get("funds.withdrawal.memoLabel", Res.getBaseCurrencyCode())).second; + Tuple2 x = addInputTextFieldInputTextField(gridPane, ++rowIndex, + Res.get("funds.withdrawal.toLabel", Res.getBaseCurrencyCode()), + Res.get("funds.withdrawal.memoLabel", Res.getBaseCurrencyCode())); + withdrawToTextField = x.first; + withdrawMemoTextField = x.second; + withdrawToTextField.setPrefWidth(Layout.MIN_WINDOW_WIDTH); + withdrawMemoTextField.setPrefWidth(Layout.MIN_WINDOW_WIDTH); Tuple3 customFeeTuple = addTopLabelInputTextFieldSlideToggleButtonRight(gridPane, ++rowIndex, Res.get("funds.withdrawal.txFee"), Res.get("funds.withdrawal.useCustomFeeValue")); @@ -442,7 +440,7 @@ private void onWithdraw() { String messageText = Res.get("shared.sendFundsDetailsWithFee", formatter.formatCoinWithCode(sendersAmount), - withdrawFromTextField.getText(), + withdrawFromAddresses, withdrawToAddress, formatter.formatCoinWithCode(fee), (double) fee.longValue() / txVsize, // no risk of div/0 since txVsize is always positive @@ -522,25 +520,17 @@ private void selectForWithdrawal(WithdrawalListItem item) { amountAsCoin = Coin.ZERO; totalAvailableAmountOfSelectedItems = Coin.ZERO; amountTextField.setText(""); - withdrawFromTextField.setText(""); + withdrawFromAddresses = ""; } if (selectedItems.size() == 1) { - withdrawFromTextField.setText(selectedItems.stream().findAny().get().getAddressEntry().getAddressString()); - withdrawFromTextField.setTooltip(null); + withdrawFromAddresses = selectedItems.stream().findAny().get().getAddressEntry().getAddressString(); } else { int abbr = Math.max(10, 66 / selectedItems.size()); String addressesShortened = selectedItems.stream() .map(e -> StringUtils.abbreviate(e.getAddressString(), abbr)) .collect(Collectors.joining(", ")); - String text = Res.get("funds.withdrawal.withdrawMultipleAddresses", addressesShortened); - withdrawFromTextField.setText(text); - - String addresses = selectedItems.stream() - .map(WithdrawalListItem::getAddressString) - .collect(Collectors.joining(",\n")); - String tooltipText = Res.get("funds.withdrawal.withdrawMultipleAddresses.tooltip", addresses); - withdrawFromTextField.setTooltip(new Tooltip(tooltipText)); + withdrawFromAddresses = Res.get("funds.withdrawal.withdrawMultipleAddresses", addressesShortened); } } else { reset(); @@ -605,9 +595,7 @@ private void sendFunds(Coin amount, Coin fee, KeyParameter aesKey, FutureCallbac } private void reset() { - withdrawFromTextField.setText(""); - withdrawFromTextField.setPromptText(Res.get("funds.withdrawal.selectAddress")); - withdrawFromTextField.setTooltip(null); + withdrawFromAddresses = ""; totalAvailableAmountOfSelectedItems = Coin.ZERO; amountAsCoin = Coin.ZERO; @@ -752,5 +740,3 @@ public void updateItem(final WithdrawalListItem item, boolean empty) { }); } } - - diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java index 197e4842835..bf3ecb76fa7 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java @@ -1169,13 +1169,18 @@ public void updateItem(final OfferBookListItem item, boolean empty) { button.setStyle(CssTheme.isDarkTheme() ? "-fx-text-fill: white" : "-fx-text-fill: #444444"); button.setOnAction(e -> onRemoveOpenOffer(offer)); - iconView2.setId("image-edit"); - button2.updateText(Res.get("shared.edit")); - button2.setId(null); - button2.setStyle(CssTheme.isDarkTheme() ? "-fx-text-fill: white" : "-fx-text-fill: #444444"); - button2.setOnAction(e -> onEditOpenOffer(offer)); - button2.setManaged(true); - button2.setVisible(true); + if (offer.isBsqSwapOffer()) { + button2.setManaged(false); + button2.setVisible(false); + } else { + iconView2.setId("image-edit"); + button2.updateText(Res.get("shared.edit")); + button2.setId(null); + button2.setStyle(CssTheme.isDarkTheme() ? "-fx-text-fill: white" : "-fx-text-fill: #444444"); + button2.setOnAction(e -> onEditOpenOffer(offer)); + button2.setManaged(true); + button2.setVisible(true); + } } else { boolean isSellOffer = OfferViewUtil.isShownAsSellOffer(offer); iconView.setId(isSellOffer ? "image-buy-white" : "image-sell-white"); diff --git a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java index b3c640df040..b2227ff8e93 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java @@ -35,6 +35,7 @@ import bisq.core.btc.wallet.Restrictions; import bisq.core.dao.DaoFacade; +import bisq.core.dao.burningman.accounting.BurningManAccountingService; import bisq.core.dao.governance.asset.AssetService; import bisq.core.filter.Filter; import bisq.core.filter.FilterManager; @@ -122,6 +123,7 @@ @FxmlView public class PreferencesView extends ActivatableViewAndModel { private final User user; + private final BurningManAccountingService burningManAccountingService; private final CoinFormatter formatter; private TextField btcExplorerTextField, bsqExplorerTextField; private ComboBox userLanguageComboBox; @@ -131,7 +133,8 @@ public class PreferencesView extends ActivatableViewAndModel fiatCurrenciesListView; @@ -154,6 +158,7 @@ public class PreferencesView extends ActivatableViewAndModel cryptoCurrenciesListView; private ComboBox cryptoCurrenciesComboBox; private Button resetDontShowAgainButton, resyncDaoFromGenesisButton, resyncDaoFromResourcesButton, + resyncBMAccFromScratchButton, resyncBMAccFromResourcesButton, editCustomBtcExplorer, editCustomBsqExplorer; private ObservableList languageCodes; private ObservableList countries; @@ -169,7 +174,7 @@ public class PreferencesView extends ActivatableViewAndModel deviationFocusedListener, bsqAverageTrimThresholdFocusedListener; private ChangeListener useCustomFeeCheckboxListener; private ChangeListener transactionFeeChangeListener; - private final boolean daoOptionsSet; + private final boolean daoFullModeFromOptionsSet; private final boolean displayStandbyModeFeature; private ChangeListener filterChangeListener; @@ -188,13 +193,16 @@ public PreferencesView(PreferencesViewModel model, DaoFacade daoFacade, Config config, User user, + BurningManAccountingService burningManAccountingService, @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter, + @Named(Config.IS_BM_FULL_NODE) boolean isBmFullNodeFromOptions, @Named(Config.RPC_USER) String rpcUser, @Named(Config.RPC_PASSWORD) String rpcPassword, @Named(Config.RPC_BLOCK_NOTIFICATION_PORT) int rpcBlockNotificationPort, @Named(Config.STORAGE_DIR) File storageDir) { super(model); this.user = user; + this.burningManAccountingService = burningManAccountingService; this.formatter = formatter; this.preferences = preferences; this.feeService = feeService; @@ -202,11 +210,12 @@ public PreferencesView(PreferencesViewModel model, this.offerFilterService = offerFilterService; this.filterManager = filterManager; this.daoFacade = daoFacade; + this.isBmFullNodeFromOptions = isBmFullNodeFromOptions; this.storageDir = storageDir; - daoOptionsSet = config.fullDaoNodeOptionSetExplicitly && - !rpcUser.isEmpty() && - !rpcPassword.isEmpty() && - rpcBlockNotificationPort != Config.UNSPECIFIED_PORT; + daoFullModeFromOptionsSet = config.fullDaoNodeOptionSetExplicitly && + rpcUser != null && !rpcUser.isEmpty() && + rpcPassword != null && !rpcPassword.isEmpty() && + rpcBlockNotificationPort > Config.UNSPECIFIED_PORT; this.displayStandbyModeFeature = Utilities.isLinux() || Utilities.isOSX() || Utilities.isWindows(); } @@ -676,12 +685,27 @@ private void initializeDisplayOptions() { } private void initializeDaoOptions() { - int rowSpan = 6; - daoOptionsTitledGroupBg = addTitledGroupBg(root, ++gridRow, rowSpan, + daoOptionsTitledGroupBg = addTitledGroupBg(root, ++gridRow, 8, Res.get("setting.preferences.daoOptions"), Layout.GROUP_DISTANCE); processBurningManAccountingDataToggleButton = addSlideToggleButton(root, gridRow, Res.get("setting.preferences.dao.processBurningManAccountingData"), Layout.FIRST_ROW_AND_GROUP_DISTANCE); + isFullBMAccountingDataNodeToggleButton = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.dao.isFullBMAccountingNode")); + isFullBMAccountingDataNodeToggleButton.setManaged(preferences.isProcessBurningManAccountingData()); + isFullBMAccountingDataNodeToggleButton.setVisible(preferences.isProcessBurningManAccountingData()); + + resyncBMAccFromScratchButton = addButton(root, ++gridRow, Res.get("setting.preferences.dao.resyncBMAccFromScratch")); + resyncBMAccFromScratchButton.setMaxWidth(Double.MAX_VALUE); + GridPane.setHgrow(resyncBMAccFromScratchButton, Priority.ALWAYS); + resyncBMAccFromScratchButton.setManaged(preferences.isProcessBurningManAccountingData()); + resyncBMAccFromScratchButton.setVisible(preferences.isProcessBurningManAccountingData()); + + resyncBMAccFromResourcesButton = addButton(root, ++gridRow, Res.get("setting.preferences.dao.resyncBMAccFromResources")); + resyncBMAccFromResourcesButton.setMaxWidth(Double.MAX_VALUE); + GridPane.setHgrow(resyncBMAccFromResourcesButton, Priority.ALWAYS); + resyncBMAccFromResourcesButton.setManaged(preferences.isProcessBurningManAccountingData()); + resyncBMAccFromResourcesButton.setVisible(preferences.isProcessBurningManAccountingData()); + fullModeDaoMonitorToggleButton = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.dao.fullModeDaoMonitor")); @@ -1118,6 +1142,36 @@ private void activateDaoPreferences() { } }); + boolean isDaoModeEnabled = isDaoModeEnabled(); + boolean isFullBMAccountingNode = (isBmFullNodeFromOptions || preferences.isFullBMAccountingNode()) && isDaoModeEnabled; + preferences.setFullBMAccountingNode(isFullBMAccountingNode); + isFullBMAccountingDataNodeToggleButton.setSelected(isFullBMAccountingNode); + + isFullBMAccountingDataNodeToggleButton.setOnAction(e -> { + if (isFullBMAccountingDataNodeToggleButton.isSelected()) { + if (isDaoModeEnabled()) { + new Popup().attention(Res.get("setting.preferences.dao.useFullBMAccountingNode.enabledDaoMode.popup")) + .actionButtonText(Res.get("shared.yes")) + .onAction(() -> { + preferences.setFullBMAccountingNode(true); + UserThread.runAfter(BisqApp.getShutDownHandler(), 500, TimeUnit.MILLISECONDS); + }) + .closeButtonText(Res.get("shared.cancel")) + .onClose(() -> isFullBMAccountingDataNodeToggleButton.setSelected(false)) + .show(); + } else { + new Popup().attention(Res.get("setting.preferences.dao.useFullBMAccountingNode.noDaoMode.popup")) + .actionButtonText(Res.get("shared.ok")) + .onAction(() -> isFullBMAccountingDataNodeToggleButton.setSelected(false)) + .onClose(() -> isFullBMAccountingDataNodeToggleButton.setSelected(false)) + .show(); + } + } else { + preferences.setFullBMAccountingNode(false); + } + }); + + fullModeDaoMonitorToggleButton.setSelected(preferences.isUseFullModeDaoMonitor()); fullModeDaoMonitorToggleButton.setOnAction(e -> { preferences.setUseFullModeDaoMonitor(fullModeDaoMonitorToggleButton.isSelected()); @@ -1133,8 +1187,7 @@ private void activateDaoPreferences() { } }); - boolean daoFullNode = preferences.isDaoFullNode(); - isDaoFullNodeToggleButton.setSelected(daoFullNode); + isDaoFullNodeToggleButton.setSelected(isDaoModeEnabled); bsqAverageTrimThresholdTextField.textProperty().addListener(bsqAverageTrimThresholdListener); bsqAverageTrimThresholdTextField.focusedProperty().addListener(bsqAverageTrimThresholdFocusedListener); @@ -1142,9 +1195,10 @@ private void activateDaoPreferences() { String rpcUser = preferences.getRpcUser(); String rpcPw = preferences.getRpcPw(); int blockNotifyPort = preferences.getBlockNotifyPort(); - if (daoFullNode && (rpcUser == null || rpcUser.isEmpty() || - rpcPw == null || rpcPw.isEmpty() || - blockNotifyPort <= 0)) { + boolean rpcDataFromPrefSet = rpcUser != null && !rpcUser.isEmpty() && + rpcPw != null && !rpcPw.isEmpty() && + blockNotifyPort > 0; + if (!daoFullModeFromOptionsSet && !rpcDataFromPrefSet) { log.warn("You have full DAO node selected but have not provided the rpc username, password and " + "block notify port. We reset daoFullNode to false"); isDaoFullNodeToggleButton.setSelected(false); @@ -1175,6 +1229,23 @@ private void activateDaoPreferences() { .closeButtonText(Res.get("shared.cancel")) .show()); + resyncBMAccFromScratchButton.setOnAction(e -> + new Popup().attention(Res.get("setting.preferences.dao.resyncBMAccFromScratch.popup")) + .actionButtonText(Res.get("setting.preferences.dao.resyncBMAccFromScratch.resync")) + .onAction(() -> burningManAccountingService.resyncAccountingDataFromScratch(BisqApp::getShutDownHandler)) + .closeButtonText(Res.get("shared.cancel")) + .show()); + + resyncBMAccFromResourcesButton.setOnAction(e -> + new Popup().attention(Res.get("setting.preferences.dao.resyncBMAccFromResources.popup")) + .actionButtonText(Res.get("setting.preferences.dao.resyncBMAccFromResources.resync")) + .onAction(() -> { + burningManAccountingService.resyncAccountingDataFromResources(); + UserThread.runAfter(BisqApp.getShutDownHandler(), 500, TimeUnit.MILLISECONDS); + }) + .closeButtonText(Res.get("shared.cancel")) + .show()); + isDaoFullNodeToggleButton.setOnAction(e -> { String key = "daoFullModeInfoShown"; if (isDaoFullNodeToggleButton.isSelected() && preferences.showAgain(key)) { @@ -1191,7 +1262,6 @@ private void activateDaoPreferences() { .width(800) .show(); } - updateDaoFields(); }); @@ -1200,6 +1270,17 @@ private void activateDaoPreferences() { blockNotifyPortTextField.textProperty().addListener(blockNotifyPortListener); } + private boolean isDaoModeEnabled() { + String rpcUser = preferences.getRpcUser(); + String rpcPw = preferences.getRpcPw(); + int blockNotifyPort = preferences.getBlockNotifyPort(); + boolean rpcDataFromPrefSet = rpcUser != null && !rpcUser.isEmpty() && + rpcPw != null && !rpcPw.isEmpty() && + blockNotifyPort > 0; + boolean daoFullModeFromPrefSet = rpcDataFromPrefSet && preferences.isDaoFullNode(); + return daoFullModeFromPrefSet || daoFullModeFromOptionsSet; + } + private void activateAutoConfirmPreferences() { preferences.findAutoConfirmSettings("XMR").ifPresent(autoConfirmSettings -> { autoConfirmXmrToggle.setSelected(autoConfirmSettings.isEnabled()); @@ -1240,7 +1321,14 @@ private void activateTradeLimitPreferences() { private void updateDaoFields() { boolean isDaoFullNode = isDaoFullNodeToggleButton.isSelected(); - GridPane.setRowSpan(daoOptionsTitledGroupBg, isDaoFullNode ? 9 : 6); + int rowSpan = 9; + if (isDaoFullNode) { + rowSpan += 3; + } + if (preferences.isProcessBurningManAccountingData()) { + rowSpan += 3; + } + GridPane.setRowSpan(daoOptionsTitledGroupBg, rowSpan); rpcUserTextField.setVisible(isDaoFullNode); rpcUserTextField.setManaged(isDaoFullNode); rpcPwTextField.setVisible(isDaoFullNode); @@ -1252,12 +1340,14 @@ private void updateDaoFields() { rpcUserTextField.clear(); rpcPwTextField.clear(); blockNotifyPortTextField.clear(); + preferences.setFullBMAccountingNode(false); + isFullBMAccountingDataNodeToggleButton.setSelected(false); } - isDaoFullNodeToggleButton.setDisable(daoOptionsSet); - rpcUserTextField.setDisable(daoOptionsSet); - rpcPwTextField.setDisable(daoOptionsSet); - blockNotifyPortTextField.setDisable(daoOptionsSet); + isDaoFullNodeToggleButton.setDisable(daoFullModeFromOptionsSet); + rpcUserTextField.setDisable(daoFullModeFromOptionsSet); + rpcPwTextField.setDisable(daoFullModeFromOptionsSet); + blockNotifyPortTextField.setDisable(daoFullModeFromOptionsSet); } @@ -1304,9 +1394,12 @@ private void deactivateDisplayPreferences() { private void deactivateDaoPreferences() { processBurningManAccountingDataToggleButton.setOnAction(null); + isFullBMAccountingDataNodeToggleButton.setOnAction(null); fullModeDaoMonitorToggleButton.setOnAction(null); resyncDaoFromResourcesButton.setOnAction(null); resyncDaoFromGenesisButton.setOnAction(null); + resyncBMAccFromScratchButton.setOnAction(null); + resyncBMAccFromResourcesButton.setOnAction(null); bsqAverageTrimThresholdTextField.textProperty().removeListener(bsqAverageTrimThresholdListener); bsqAverageTrimThresholdTextField.focusedProperty().removeListener(bsqAverageTrimThresholdFocusedListener); isDaoFullNodeToggleButton.setOnAction(null); diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 78be5ce9392..4e74fc28728 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -1197,6 +1197,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1717,6 +1754,14 @@ + + + + + + + + @@ -1725,6 +1770,14 @@ + + + + + + + + @@ -1733,6 +1786,14 @@ + + + + + + + + @@ -1741,6 +1802,14 @@ + + + + + + + + diff --git a/platform/build.gradle b/platform/build.gradle new file mode 100644 index 00000000000..5036fd8e6aa --- /dev/null +++ b/platform/build.gradle @@ -0,0 +1,9 @@ +plugins { + id 'java-platform' +} + +dependencies { + constraints { + api libs.protobuf.java + } +} diff --git a/proto/build.gradle b/proto/build.gradle index a52ad806fa4..d52577e3fd8 100644 --- a/proto/build.gradle +++ b/proto/build.gradle @@ -3,8 +3,10 @@ plugins { } apply plugin: 'com.google.protobuf' +apply plugin: 'idea' dependencies { + implementation enforcedPlatform(project(':platform')) annotationProcessor libs.lombok compileOnly libs.javax.annotation compileOnly libs.lombok @@ -23,21 +25,16 @@ dependencies { } } -sourceSets.main.java.srcDirs += [ - 'build/generated/source/proto/main/grpc', - 'build/generated/source/proto/main/java' -] - protobuf { protoc { - artifact = "com.google.protobuf:protoc:${protocVersion}" + artifact = "com.google.protobuf:protoc:3.19.1" } plugins { grpc { - artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" + artifact = "io.grpc:protoc-gen-grpc-java:1.42.1" } } generateProtoTasks { - all()*.plugins { grpc {} } + ofSourceSet('main')*.plugins { grpc {} } } } diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 2d276a327a4..0de2b62a6e5 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -1972,6 +1972,7 @@ message PreferencesPayload { int64 user_defined_trade_limit = 67; bool user_has_raised_trade_limit = 68; bool process_burning_man_accounting_data = 69; + bool is_full_b_m_accounting_node = 70; } message AutoConfirmSettings { diff --git a/settings.gradle b/settings.gradle index 530d25e2f17..5f45142f94f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -16,5 +16,7 @@ include 'desktop' include 'seednode' include 'statsnode' include 'apitest' +include 'platform' +include 'code-coverage-report' rootProject.name = 'bisq'