diff --git a/build.gradle b/build.gradle
index 92e2892da5..d548e5a3a2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -339,6 +339,8 @@ configure(project(':monitor')) {
}
dependencies {
+ compile project(':p2p')
+ compile project(':core')
compile "org.slf4j:slf4j-api:$slf4jVersion"
compile "ch.qos.logback:logback-core:$logbackVersion"
compile "ch.qos.logback:logback-classic:$logbackVersion"
diff --git a/common/src/main/java/bisq/common/util/PermutationUtil.java b/common/src/main/java/bisq/common/util/PermutationUtil.java
new file mode 100644
index 0000000000..dbcab390a1
--- /dev/null
+++ b/common/src/main/java/bisq/common/util/PermutationUtil.java
@@ -0,0 +1,107 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.common.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class PermutationUtil {
+
+ /**
+ * @param list Original list
+ * @param indicesToRemove List of indices to remove
+ * @param Type of List items
+ * @return Partial list where items at indices of indicesToRemove have been removed
+ */
+ public static List getPartialList(List list, List indicesToRemove) {
+ List altered = new ArrayList<>(list);
+
+ // Eliminate duplicates
+ indicesToRemove = new ArrayList<>(new HashSet<>(indicesToRemove));
+
+ // Sort
+ Collections.sort(indicesToRemove);
+
+ // Reverse list.
+ // We need to remove from highest index downwards to not change order of remaining indices
+ Collections.reverse(indicesToRemove);
+
+ indicesToRemove.forEach(index -> {
+ if (altered.size() > index && index >= 0)
+ altered.remove((int) index);
+ });
+ return altered;
+ }
+
+ /**
+ * Returns a list of all possible permutations of a give sorted list ignoring duplicates.
+ * E.g. List [A,B,C] results in this list of permutations: [[A], [B], [A,B], [C], [A,C], [B,C], [A,B,C]]
+ * Number of variations and iterations grows with 2^n - 1 where n is the number of items in the list.
+ * With 20 items we reach about 1 million iterations and it takes about 0.5 sec.
+ * To avoid performance issues we added the maxIterations parameter to stop once the number of iterations has
+ * reached the maxIterations and return in such a case the list of permutations we have been able to create.
+ * Depending on the type of object which is stored in the list the memory usage should be considered as well for
+ * choosing the right maxIterations value.
+ *
+ * @param list List from which we create permutations
+ * @param maxIterations Max. number of iterations including inner iterations
+ * @param Type of list items
+ * @return List of possible permutations of the original list
+ */
+ public static List> findAllPermutations(List list, int maxIterations) {
+ List> result = new ArrayList<>();
+ int counter = 0;
+ long ts = System.currentTimeMillis();
+ for (T item : list) {
+ counter++;
+ if (counter > maxIterations) {
+ log.warn("We reached maxIterations of our allowed iterations and return current state of the result. " +
+ "counter={}", counter);
+ return result;
+ }
+
+ List> subLists = new ArrayList<>();
+ for (int n = 0; n < result.size(); n++) {
+ counter++;
+ if (counter > maxIterations) {
+ log.warn("We reached maxIterations of our allowed iterations and return current state of the result. " +
+ "counter={}", counter);
+ return result;
+ }
+ List subList = new ArrayList<>(result.get(n));
+ subList.add(item);
+ subLists.add(subList);
+ }
+
+ // add single item
+ result.add(new ArrayList<>(Collections.singletonList(item)));
+
+ // add subLists
+ result.addAll(subLists);
+ }
+
+ log.info("findAllPermutations took {} ms for {} items and {} iterations. Heap size used: {} MB",
+ (System.currentTimeMillis() - ts), list.size(), counter, Profiler.getUsedMemoryInMB());
+ return result;
+ }
+}
diff --git a/common/src/test/java/bisq/common/util/PermutationTest.java b/common/src/test/java/bisq/common/util/PermutationTest.java
new file mode 100644
index 0000000000..149cf9c971
--- /dev/null
+++ b/common/src/test/java/bisq/common/util/PermutationTest.java
@@ -0,0 +1,432 @@
+/*
+ * This file is part of Bisq.
+ *
+ * bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with bisq. If not, see .
+ */
+
+package bisq.common.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertTrue;
+
+public class PermutationTest {
+
+
+ @Test
+ public void testGetPartialList() {
+ String blindVote0 = "blindVote0";
+ String blindVote1 = "blindVote1";
+ String blindVote2 = "blindVote2";
+ String blindVote3 = "blindVote3";
+ String blindVote4 = "blindVote4";
+ String blindVote5 = "blindVote5";
+
+ List list = new ArrayList<>(Arrays.asList(blindVote0, blindVote1, blindVote2, blindVote3, blindVote4, blindVote5));
+ List indicesToRemove = Arrays.asList(0, 3);
+ List expected = new ArrayList<>(Arrays.asList(blindVote1, blindVote2, blindVote4, blindVote5));
+ List result = PermutationUtil.getPartialList(list, indicesToRemove);
+ assertTrue(expected.toString().equals(result.toString()));
+
+ // remove nothing
+ indicesToRemove = new ArrayList<>();
+ expected = new ArrayList<>(list);
+ result = PermutationUtil.getPartialList(list, indicesToRemove);
+ assertTrue(expected.toString().equals(result.toString()));
+
+ // remove first
+ indicesToRemove = Collections.singletonList(0);
+ expected = new ArrayList<>(list);
+ expected.remove(0);
+ result = PermutationUtil.getPartialList(list, indicesToRemove);
+ assertTrue(expected.toString().equals(result.toString()));
+
+ // remove last
+ indicesToRemove = Collections.singletonList(5);
+ expected = new ArrayList<>(list);
+ expected.remove(5);
+ result = PermutationUtil.getPartialList(list, indicesToRemove);
+ assertTrue(expected.toString().equals(result.toString()));
+
+ // remove all
+ indicesToRemove = Arrays.asList(0, 1, 2, 3, 4, 5);
+ expected = new ArrayList<>();
+ result = PermutationUtil.getPartialList(list, indicesToRemove);
+ assertTrue(expected.toString().equals(result.toString()));
+
+ // wrong sorting of indices
+ indicesToRemove = Arrays.asList(4, 0, 1);
+ expected = expected = new ArrayList<>(Arrays.asList(blindVote2, blindVote3, blindVote5));
+ result = PermutationUtil.getPartialList(list, indicesToRemove);
+ assertTrue(expected.toString().equals(result.toString()));
+
+ // wrong sorting of indices
+ indicesToRemove = Arrays.asList(0, 0);
+ expected = new ArrayList<>(Arrays.asList(blindVote1, blindVote2, blindVote3, blindVote4, blindVote5));
+ result = PermutationUtil.getPartialList(list, indicesToRemove);
+ assertTrue(expected.toString().equals(result.toString()));
+
+ // don't remove as invalid index
+ indicesToRemove = Collections.singletonList(9);
+ expected = new ArrayList<>(list);
+ result = PermutationUtil.getPartialList(list, indicesToRemove);
+ assertTrue(expected.toString().equals(result.toString()));
+
+ // don't remove as invalid index
+ indicesToRemove = Collections.singletonList(-2);
+ expected = new ArrayList<>(list);
+ result = PermutationUtil.getPartialList(list, indicesToRemove);
+ assertTrue(expected.toString().equals(result.toString()));
+ }
+
+ @Test
+ public void testFindAllPermutations() {
+ String blindVote0 = "blindVote0";
+ String blindVote1 = "blindVote1";
+ String blindVote2 = "blindVote2";
+ String blindVote3 = "blindVote3";
+ String blindVote4 = "blindVote4";
+
+ // Up to about 1M iterations performance is acceptable (0.5 sec)
+ // findAllPermutations took 580 ms for 20 items and 1048575 iterations
+ // findAllPermutations took 10 ms for 15 items and 32767 iterations
+ // findAllPermutations took 0 ms for 10 items and 1023 iterations
+ int limit = 1048575;
+ List list;
+ List> expected;
+ List> result;
+ List subList;
+
+
+ /* list = new ArrayList<>();
+ for (int i = 0; i < 20; i++) {
+ list.add("blindVote"+i);
+ }
+ PermutationUtil.findAllPermutations(list, limit);*/
+
+
+ list = new ArrayList<>();
+ expected = new ArrayList<>();
+ result = PermutationUtil.findAllPermutations(list, limit);
+ assertTrue(expected.toString().equals(result.toString()));
+
+ list = new ArrayList<>(Arrays.asList(blindVote0));
+ expected = new ArrayList<>();
+ expected.add(list);
+ result = PermutationUtil.findAllPermutations(list, limit);
+ assertTrue(expected.toString().equals(result.toString()));
+
+ // 2 items -> 3 variations
+ list = new ArrayList<>(Arrays.asList(blindVote0, blindVote1));
+ expected = new ArrayList<>();
+ expected.add(Arrays.asList(list.get(0)));
+
+ expected.add(Arrays.asList(list.get(1)));
+
+ subList = new ArrayList<>();
+ subList.add(list.get(0));
+ subList.add(list.get(1));
+ expected.add(subList);
+
+ result = PermutationUtil.findAllPermutations(list, limit);
+ assertTrue(expected.toString().equals(result.toString()));
+
+ // 3 items -> 7 variations
+ list = new ArrayList<>(Arrays.asList(blindVote0, blindVote1, blindVote2));
+ expected = new ArrayList<>();
+ expected.add(Arrays.asList(list.get(0)));
+
+ expected.add(Arrays.asList(list.get(1)));
+
+ subList = new ArrayList<>();
+ subList.add(list.get(0));
+ subList.add(list.get(1));
+ expected.add(subList);
+
+ expected.add(Arrays.asList(list.get(2)));
+
+ subList = new ArrayList<>();
+ subList.add(list.get(0));
+ subList.add(list.get(2));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(1));
+ subList.add(list.get(2));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(0));
+ subList.add(list.get(1));
+ subList.add(list.get(2));
+ expected.add(subList);
+
+ result = PermutationUtil.findAllPermutations(list, limit);
+ assertTrue(expected.toString().equals(result.toString()));
+
+ // 4 items -> 15 variations
+ list = new ArrayList<>(Arrays.asList(blindVote0, blindVote1, blindVote2, blindVote3));
+ expected = new ArrayList<>();
+ expected.add(Arrays.asList(list.get(0)));
+
+ expected.add(Arrays.asList(list.get(1)));
+
+ subList = new ArrayList<>();
+ subList.add(list.get(0));
+ subList.add(list.get(1));
+ expected.add(subList);
+
+ expected.add(Arrays.asList(list.get(2)));
+
+ subList = new ArrayList<>();
+ subList.add(list.get(0));
+ subList.add(list.get(2));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(1));
+ subList.add(list.get(2));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(0));
+ subList.add(list.get(1));
+ subList.add(list.get(2));
+ expected.add(subList);
+
+ expected.add(Arrays.asList(list.get(3)));
+
+ subList = new ArrayList<>();
+ subList.add(list.get(0));
+ subList.add(list.get(3));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(1));
+ subList.add(list.get(3));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(0));
+ subList.add(list.get(1));
+ subList.add(list.get(3));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(2));
+ subList.add(list.get(3));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(0));
+ subList.add(list.get(2));
+ subList.add(list.get(3));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(1));
+ subList.add(list.get(2));
+ subList.add(list.get(3));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(0));
+ subList.add(list.get(1));
+ subList.add(list.get(2));
+ subList.add(list.get(3));
+ expected.add(subList);
+
+ result = PermutationUtil.findAllPermutations(list, limit);
+ assertTrue(expected.toString().equals(result.toString()));
+
+
+ // 5 items -> 31 variations
+ list = new ArrayList<>(Arrays.asList(blindVote0, blindVote1, blindVote2, blindVote3, blindVote4));
+ expected = new ArrayList<>();
+ expected.add(Arrays.asList(list.get(0)));
+
+ expected.add(Arrays.asList(list.get(1)));
+
+ subList = new ArrayList<>();
+ subList.add(list.get(0));
+ subList.add(list.get(1));
+ expected.add(subList);
+
+ expected.add(Arrays.asList(list.get(2)));
+
+ subList = new ArrayList<>();
+ subList.add(list.get(0));
+ subList.add(list.get(2));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(1));
+ subList.add(list.get(2));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(0));
+ subList.add(list.get(1));
+ subList.add(list.get(2));
+ expected.add(subList);
+
+ expected.add(Arrays.asList(list.get(3)));
+
+ subList = new ArrayList<>();
+ subList.add(list.get(0));
+ subList.add(list.get(3));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(1));
+ subList.add(list.get(3));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(0));
+ subList.add(list.get(1));
+ subList.add(list.get(3));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(2));
+ subList.add(list.get(3));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(0));
+ subList.add(list.get(2));
+ subList.add(list.get(3));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(1));
+ subList.add(list.get(2));
+ subList.add(list.get(3));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(0));
+ subList.add(list.get(1));
+ subList.add(list.get(2));
+ subList.add(list.get(3));
+ expected.add(subList);
+
+ expected.add(Arrays.asList(list.get(4)));
+
+ subList = new ArrayList<>();
+ subList.add(list.get(0));
+ subList.add(list.get(4));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(1));
+ subList.add(list.get(4));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(0));
+ subList.add(list.get(1));
+ subList.add(list.get(4));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(2));
+ subList.add(list.get(4));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(0));
+ subList.add(list.get(2));
+ subList.add(list.get(4));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(1));
+ subList.add(list.get(2));
+ subList.add(list.get(4));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(0));
+ subList.add(list.get(1));
+ subList.add(list.get(2));
+ subList.add(list.get(4));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(3));
+ subList.add(list.get(4));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(0));
+ subList.add(list.get(3));
+ subList.add(list.get(4));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(1));
+ subList.add(list.get(3));
+ subList.add(list.get(4));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(0));
+ subList.add(list.get(1));
+ subList.add(list.get(3));
+ subList.add(list.get(4));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(2));
+ subList.add(list.get(3));
+ subList.add(list.get(4));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(0));
+ subList.add(list.get(2));
+ subList.add(list.get(3));
+ subList.add(list.get(4));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(1));
+ subList.add(list.get(2));
+ subList.add(list.get(3));
+ subList.add(list.get(4));
+ expected.add(subList);
+
+ subList = new ArrayList<>();
+ subList.add(list.get(0));
+ subList.add(list.get(1));
+ subList.add(list.get(2));
+ subList.add(list.get(3));
+ subList.add(list.get(4));
+ expected.add(subList);
+
+ result = PermutationUtil.findAllPermutations(list, limit);
+ assertTrue(expected.toString().equals(result.toString()));
+
+
+ }
+
+
+}
diff --git a/core/src/main/java/bisq/core/CoreModule.java b/core/src/main/java/bisq/core/CoreModule.java
index db41abc221..6058ba71f3 100644
--- a/core/src/main/java/bisq/core/CoreModule.java
+++ b/core/src/main/java/bisq/core/CoreModule.java
@@ -23,6 +23,7 @@ import bisq.core.app.AvoidStandbyModeService;
import bisq.core.app.BisqEnvironment;
import bisq.core.app.BisqSetup;
import bisq.core.app.P2PNetworkSetup;
+import bisq.core.app.TorSetup;
import bisq.core.app.WalletAppSetup;
import bisq.core.arbitration.ArbitratorModule;
import bisq.core.btc.BitcoinModule;
@@ -80,6 +81,7 @@ public class CoreModule extends AppModule {
@Override
protected void configure() {
bind(BisqSetup.class).in(Singleton.class);
+ bind(TorSetup.class).in(Singleton.class);
bind(P2PNetworkSetup.class).in(Singleton.class);
bind(WalletAppSetup.class).in(Singleton.class);
diff --git a/core/src/main/java/bisq/core/app/BisqHeadlessApp.java b/core/src/main/java/bisq/core/app/BisqHeadlessApp.java
index 3bb23f40f2..f7ece906fb 100644
--- a/core/src/main/java/bisq/core/app/BisqHeadlessApp.java
+++ b/core/src/main/java/bisq/core/app/BisqHeadlessApp.java
@@ -78,26 +78,26 @@ public class BisqHeadlessApp implements HeadlessApp {
log.info("onDisplayTacHandler: We accept the tacs automatically in headless mode");
acceptedHandler.run();
});
- bisqSetup.setCryptoSetupFailedHandler(msg -> log.info("onCryptoSetupFailedHandler: msg={}", msg));
+ bisqSetup.setCryptoSetupFailedHandler(msg -> log.error("onCryptoSetupFailedHandler: msg={}", msg));
bisqSetup.setDisplayTorNetworkSettingsHandler(show -> log.info("onDisplayTorNetworkSettingsHandler: show={}", show));
- bisqSetup.setSpvFileCorruptedHandler(msg -> log.info("onSpvFileCorruptedHandler: msg={}", msg));
- bisqSetup.setChainFileLockedExceptionHandler(msg -> log.info("onChainFileLockedExceptionHandler: msg={}", msg));
+ bisqSetup.setSpvFileCorruptedHandler(msg -> log.error("onSpvFileCorruptedHandler: msg={}", msg));
+ bisqSetup.setChainFileLockedExceptionHandler(msg -> log.error("onChainFileLockedExceptionHandler: msg={}", msg));
bisqSetup.setLockedUpFundsHandler(msg -> log.info("onLockedUpFundsHandler: msg={}", msg));
bisqSetup.setShowFirstPopupIfResyncSPVRequestedHandler(() -> log.info("onShowFirstPopupIfResyncSPVRequestedHandler"));
bisqSetup.setRequestWalletPasswordHandler(aesKeyHandler -> log.info("onRequestWalletPasswordHandler"));
bisqSetup.setDisplayUpdateHandler((alert, key) -> log.info("onDisplayUpdateHandler"));
bisqSetup.setDisplayAlertHandler(alert -> log.info("onDisplayAlertHandler. alert={}", alert));
bisqSetup.setDisplayPrivateNotificationHandler(privateNotification -> log.info("onDisplayPrivateNotificationHandler. privateNotification={}", privateNotification));
- bisqSetup.setDaoErrorMessageHandler(errorMessage -> log.info("onDaoErrorMessageHandler. errorMessage={}", errorMessage));
- bisqSetup.setDaoWarnMessageHandler(warnMessage -> log.info("onDaoWarnMessageHandler. warnMessage={}", warnMessage));
+ bisqSetup.setDaoErrorMessageHandler(errorMessage -> log.error("onDaoErrorMessageHandler. errorMessage={}", errorMessage));
+ bisqSetup.setDaoWarnMessageHandler(warnMessage -> log.warn("onDaoWarnMessageHandler. warnMessage={}", warnMessage));
bisqSetup.setDisplaySecurityRecommendationHandler(key -> log.info("onDisplaySecurityRecommendationHandler"));
bisqSetup.setDisplayLocalhostHandler(key -> log.info("onDisplayLocalhostHandler"));
- bisqSetup.setWrongOSArchitectureHandler(msg -> log.info("onWrongOSArchitectureHandler. msg={}", msg));
- bisqSetup.setVoteResultExceptionHandler(voteResultException -> log.info("voteResultException={}", voteResultException));
+ bisqSetup.setWrongOSArchitectureHandler(msg -> log.error("onWrongOSArchitectureHandler. msg={}", msg));
+ bisqSetup.setVoteResultExceptionHandler(voteResultException -> log.warn("voteResultException={}", voteResultException));
//TODO move to bisqSetup
- corruptedDatabaseFilesHandler.getCorruptedDatabaseFiles().ifPresent(files -> log.info("getCorruptedDatabaseFiles. files={}", files));
- tradeManager.setTakeOfferRequestErrorMessageHandler(errorMessage -> log.info("onTakeOfferRequestErrorMessageHandler"));
+ corruptedDatabaseFilesHandler.getCorruptedDatabaseFiles().ifPresent(files -> log.warn("getCorruptedDatabaseFiles. files={}", files));
+ tradeManager.setTakeOfferRequestErrorMessageHandler(errorMessage -> log.error("onTakeOfferRequestErrorMessageHandler"));
}
public void stop() {
diff --git a/core/src/main/java/bisq/core/app/BisqSetup.java b/core/src/main/java/bisq/core/app/BisqSetup.java
index cb8e6aa997..cbc70acad2 100644
--- a/core/src/main/java/bisq/core/app/BisqSetup.java
+++ b/core/src/main/java/bisq/core/app/BisqSetup.java
@@ -150,6 +150,7 @@ public class BisqSetup {
private final VoteResultService voteResultService;
private final AssetTradeActivityCheck tradeActivityCheck;
private final AssetService assetService;
+ private final TorSetup torSetup;
private final BSFormatter formatter;
@Setter
@Nullable
@@ -226,6 +227,7 @@ public class BisqSetup {
VoteResultService voteResultService,
AssetTradeActivityCheck tradeActivityCheck,
AssetService assetService,
+ TorSetup torSetup,
BSFormatter formatter) {
@@ -264,6 +266,7 @@ public class BisqSetup {
this.voteResultService = voteResultService;
this.tradeActivityCheck = tradeActivityCheck;
this.assetService = assetService;
+ this.torSetup = torSetup;
this.formatter = formatter;
}
@@ -286,6 +289,7 @@ public class BisqSetup {
}
private void step3() {
+ torSetup.cleanupTorFiles();
readMapsFromResources();
checkCryptoSetup();
checkForCorrectOSArchitecture();
diff --git a/core/src/main/java/bisq/core/app/TorSetup.java b/core/src/main/java/bisq/core/app/TorSetup.java
new file mode 100644
index 0000000000..238e23e97a
--- /dev/null
+++ b/core/src/main/java/bisq/core/app/TorSetup.java
@@ -0,0 +1,69 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.core.app;
+
+import bisq.network.NetworkOptionKeys;
+
+import bisq.common.handlers.ErrorMessageHandler;
+import bisq.common.storage.FileUtil;
+
+import com.google.inject.name.Named;
+
+import javax.inject.Inject;
+
+import java.nio.file.Paths;
+
+import java.io.File;
+import java.io.IOException;
+
+import lombok.extern.slf4j.Slf4j;
+
+import javax.annotation.Nullable;
+
+@Slf4j
+public class TorSetup {
+ private File torDir;
+
+ @Inject
+ public TorSetup(@Named(NetworkOptionKeys.TOR_DIR) File torDir) {
+ this.torDir = torDir;
+ }
+
+ public void cleanupTorFiles() {
+ cleanupTorFiles(null, null);
+ }
+
+ // We get sometimes Tor startup problems which is related to some tor files in the tor directory. It happens
+ // more often if the application got killed (not graceful shutdown).
+ // Creating all tor files newly takes about 3-4 sec. longer and it does not benefit from cache files.
+ // TODO: We should fix those startup problems in the netlayer library, once fixed there we can remove that call at the
+ // Bisq startup again.
+ public void cleanupTorFiles(@Nullable Runnable resultHandler, @Nullable ErrorMessageHandler errorMessageHandler) {
+ File hiddenservice = new File(Paths.get(torDir.getAbsolutePath(), "hiddenservice").toString());
+ try {
+ FileUtil.deleteDirectory(torDir, hiddenservice, true);
+ if (resultHandler != null)
+ resultHandler.run();
+ } catch (IOException e) {
+ e.printStackTrace();
+ log.error(e.toString());
+ if (errorMessageHandler != null)
+ errorMessageHandler.handleErrorMessage(e.toString());
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/app/misc/AppSetupWithP2P.java b/core/src/main/java/bisq/core/app/misc/AppSetupWithP2P.java
index 89adeacab2..1cb08ab7f8 100644
--- a/core/src/main/java/bisq/core/app/misc/AppSetupWithP2P.java
+++ b/core/src/main/java/bisq/core/app/misc/AppSetupWithP2P.java
@@ -18,6 +18,7 @@
package bisq.core.app.misc;
import bisq.core.app.SetupUtils;
+import bisq.core.app.TorSetup;
import bisq.core.filter.FilterManager;
import bisq.core.payment.AccountAgeWitnessService;
import bisq.core.trade.statistics.TradeStatisticsManager;
@@ -46,6 +47,7 @@ public class AppSetupWithP2P extends AppSetup {
protected final P2PService p2PService;
protected final AccountAgeWitnessService accountAgeWitnessService;
protected final FilterManager filterManager;
+ private final TorSetup torSetup;
protected BooleanProperty p2pNetWorkReady;
protected final TradeStatisticsManager tradeStatisticsManager;
protected ArrayList persistedDataHosts;
@@ -56,21 +58,24 @@ public class AppSetupWithP2P extends AppSetup {
P2PService p2PService,
TradeStatisticsManager tradeStatisticsManager,
AccountAgeWitnessService accountAgeWitnessService,
- FilterManager filterManager) {
+ FilterManager filterManager,
+ TorSetup torSetup) {
super(encryptionService, keyRing);
this.p2PService = p2PService;
this.tradeStatisticsManager = tradeStatisticsManager;
this.accountAgeWitnessService = accountAgeWitnessService;
this.filterManager = filterManager;
+ this.torSetup = torSetup;
this.persistedDataHosts = new ArrayList<>();
}
@Override
public void initPersistedDataHosts() {
+ torSetup.cleanupTorFiles();
persistedDataHosts.add(p2PService);
// we apply at startup the reading of persisted data but don't want to get it triggered in the constructor
- persistedDataHosts.stream().forEach(e -> {
+ persistedDataHosts.forEach(e -> {
try {
log.info("call readPersisted at " + e.getClass().getSimpleName());
e.readPersisted();
diff --git a/core/src/main/java/bisq/core/app/misc/AppSetupWithP2PAndDAO.java b/core/src/main/java/bisq/core/app/misc/AppSetupWithP2PAndDAO.java
index 50628e336e..4a2a89c295 100644
--- a/core/src/main/java/bisq/core/app/misc/AppSetupWithP2PAndDAO.java
+++ b/core/src/main/java/bisq/core/app/misc/AppSetupWithP2PAndDAO.java
@@ -17,6 +17,7 @@
package bisq.core.app.misc;
+import bisq.core.app.TorSetup;
import bisq.core.dao.DaoOptionKeys;
import bisq.core.dao.DaoSetup;
import bisq.core.dao.governance.ballot.BallotListService;
@@ -57,13 +58,15 @@ public class AppSetupWithP2PAndDAO extends AppSetupWithP2P {
MyProposalListService myProposalListService,
MyReputationListService myReputationListService,
MyProofOfBurnListService myProofOfBurnListService,
+ TorSetup torSetup,
@Named(DaoOptionKeys.DAO_ACTIVATED) boolean daoActivated) {
super(encryptionService,
keyRing,
p2PService,
tradeStatisticsManager,
accountAgeWitnessService,
- filterManager);
+ filterManager,
+ torSetup);
this.daoSetup = daoSetup;
diff --git a/core/src/main/java/bisq/core/app/misc/ModuleForAppWithP2p.java b/core/src/main/java/bisq/core/app/misc/ModuleForAppWithP2p.java
index 64cfcb465e..16d1c87ea7 100644
--- a/core/src/main/java/bisq/core/app/misc/ModuleForAppWithP2p.java
+++ b/core/src/main/java/bisq/core/app/misc/ModuleForAppWithP2p.java
@@ -20,6 +20,7 @@ package bisq.core.app.misc;
import bisq.core.alert.AlertModule;
import bisq.core.app.AppOptionKeys;
import bisq.core.app.BisqEnvironment;
+import bisq.core.app.TorSetup;
import bisq.core.arbitration.ArbitratorModule;
import bisq.core.btc.BitcoinModule;
import bisq.core.dao.DaoModule;
@@ -74,6 +75,7 @@ public class ModuleForAppWithP2p extends AppModule {
bind(PersistenceProtoResolver.class).to(CorePersistenceProtoResolver.class).in(Singleton.class);
bind(Preferences.class).in(Singleton.class);
bind(BridgeAddressProvider.class).to(Preferences.class).in(Singleton.class);
+ bind(TorSetup.class).in(Singleton.class);
bind(SeedNodeAddressLookup.class).in(Singleton.class);
bind(SeedNodeRepository.class).to(DefaultSeedNodeRepository.class).in(Singleton.class);
diff --git a/core/src/main/java/bisq/core/btc/nodes/BtcNodes.java b/core/src/main/java/bisq/core/btc/nodes/BtcNodes.java
index 4f4c243c38..63656db1eb 100644
--- a/core/src/main/java/bisq/core/btc/nodes/BtcNodes.java
+++ b/core/src/main/java/bisq/core/btc/nodes/BtcNodes.java
@@ -68,6 +68,9 @@ public class BtcNodes {
new BtcNode("btc1.sqrrm.net", "3r44ddzjitznyahw.onion", "185.25.48.184", BtcNode.DEFAULT_PORT, "@sqrrm"),
new BtcNode("btc2.sqrrm.net", "i3a5xtzfm4xwtybd.onion", "81.171.22.143", BtcNode.DEFAULT_PORT, "@sqrrm"),
+ // KanoczTomas
+ new BtcNode("btc.ispol.sk", "mbm6ffx6j5ygi2ck.onion", "193.58.196.212", BtcNode.DEFAULT_PORT, "@KanoczTomas"),
+
// sgeisler
new BtcNode("bcwat.ch", "z33nukt7ngik3cpe.onion", "5.189.166.193", BtcNode.DEFAULT_PORT, "@sgeisler"),
diff --git a/core/src/main/java/bisq/core/dao/DaoFacade.java b/core/src/main/java/bisq/core/dao/DaoFacade.java
index d2a433cd2b..59f636b75b 100644
--- a/core/src/main/java/bisq/core/dao/DaoFacade.java
+++ b/core/src/main/java/bisq/core/dao/DaoFacade.java
@@ -397,17 +397,17 @@ public class DaoFacade implements DaoSetupService {
case PROPOSAL:
break;
case BREAK1:
- firstBlock++;
+ firstBlock--;
break;
case BLIND_VOTE:
break;
case BREAK2:
- firstBlock++;
+ firstBlock--;
break;
case VOTE_REVEAL:
break;
case BREAK3:
- firstBlock++;
+ firstBlock--;
break;
case RESULT:
break;
diff --git a/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java b/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java
index 05541280fe..d27c2c9b13 100644
--- a/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java
+++ b/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java
@@ -46,7 +46,6 @@ import bisq.core.dao.state.model.governance.Proposal;
import bisq.network.p2p.P2PService;
import bisq.common.UserThread;
-import bisq.common.app.DevEnv;
import bisq.common.crypto.CryptoException;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ExceptionHandler;
@@ -350,7 +349,8 @@ public class MyBlindVoteListService implements PersistedDataHost, DaoStateListen
private void rePublishOnceWellConnected() {
int minPeers = BisqEnvironment.getBaseCurrencyNetwork().isMainnet() ? 4 : 1;
- if ((p2PService.getNumConnectedPeers().get() > minPeers && p2PService.isBootstrapped()) || DevEnv.isDevMode()) {
+ if ((p2PService.getNumConnectedPeers().get() >= minPeers && p2PService.isBootstrapped()) ||
+ BisqEnvironment.getBaseCurrencyNetwork().isRegtest()) {
int chainHeight = periodService.getChainHeight();
myBlindVoteList.stream()
.filter(blindVote -> periodService.isTxInPhaseAndCycle(blindVote.getTxId(),
diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/MyProposalListService.java b/core/src/main/java/bisq/core/dao/governance/proposal/MyProposalListService.java
index 4dae984c21..1fd63afe18 100644
--- a/core/src/main/java/bisq/core/dao/governance/proposal/MyProposalListService.java
+++ b/core/src/main/java/bisq/core/dao/governance/proposal/MyProposalListService.java
@@ -225,7 +225,8 @@ public class MyProposalListService implements PersistedDataHost, DaoStateListene
private void rePublishOnceWellConnected() {
int minPeers = BisqEnvironment.getBaseCurrencyNetwork().isMainnet() ? 4 : 1;
- if ((p2PService.getNumConnectedPeers().get() > minPeers && p2PService.isBootstrapped())) {
+ if ((p2PService.getNumConnectedPeers().get() >= minPeers && p2PService.isBootstrapped()) ||
+ BisqEnvironment.getBaseCurrencyNetwork().isRegtest()) {
p2PService.getNumConnectedPeers().removeListener(numConnectedPeersListener);
rePublish();
}
diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/ProposalService.java b/core/src/main/java/bisq/core/dao/governance/proposal/ProposalService.java
index 238a93d1be..97807b5dd4 100644
--- a/core/src/main/java/bisq/core/dao/governance/proposal/ProposalService.java
+++ b/core/src/main/java/bisq/core/dao/governance/proposal/ProposalService.java
@@ -74,7 +74,6 @@ public class ProposalService implements HashMapChangedListener, AppendOnlyDataSt
// different data collections due the eventually consistency of the P2P network.
@Getter
private final ObservableList proposalPayloads = FXCollections.observableArrayList();
- private boolean parsingComplete;
///////////////////////////////////////////////////////////////////////////////////////////
@@ -163,17 +162,13 @@ public class ProposalService implements HashMapChangedListener, AppendOnlyDataSt
if (block.getHeight() == heightForRepublishing) {
// We only republish if we are completed with parsing old blocks, otherwise we would republish old
// proposals all the time
- if (parsingComplete) {
- publishToAppendOnlyDataStore();
- fillListFromAppendOnlyDataStore();
- }
+ publishToAppendOnlyDataStore();
+ fillListFromAppendOnlyDataStore();
}
}
@Override
public void onParseBlockChainComplete() {
- parsingComplete = true;
-
// Fill the lists with the data we have collected in out stores.
fillListFromProtectedStore();
fillListFromAppendOnlyDataStore();
@@ -181,10 +176,9 @@ public class ProposalService implements HashMapChangedListener, AppendOnlyDataSt
///////////////////////////////////////////////////////////////////////////////////////////
- // Getter
+ // API
///////////////////////////////////////////////////////////////////////////////////////////
-
public List getValidatedProposals() {
return proposalPayloads.stream()
.map(ProposalPayload::getProposal)
diff --git a/core/src/main/java/bisq/core/dao/governance/voteresult/MissingDataRequestService.java b/core/src/main/java/bisq/core/dao/governance/voteresult/MissingDataRequestService.java
index d0b47df820..24d38f03d2 100644
--- a/core/src/main/java/bisq/core/dao/governance/voteresult/MissingDataRequestService.java
+++ b/core/src/main/java/bisq/core/dao/governance/voteresult/MissingDataRequestService.java
@@ -18,25 +18,41 @@
package bisq.core.dao.governance.voteresult;
import bisq.core.dao.DaoSetupService;
+import bisq.core.dao.governance.blindvote.BlindVoteListService;
import bisq.core.dao.governance.blindvote.network.RepublishGovernanceDataHandler;
+import bisq.core.dao.governance.blindvote.storage.BlindVotePayload;
+import bisq.core.dao.governance.proposal.ProposalService;
+import bisq.core.dao.governance.proposal.storage.appendonly.ProposalPayload;
+
+import bisq.network.p2p.P2PService;
+
+import bisq.common.UserThread;
import javax.inject.Inject;
-import javafx.collections.FXCollections;
-import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
-import lombok.Getter;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
public class MissingDataRequestService implements DaoSetupService {
private final RepublishGovernanceDataHandler republishGovernanceDataHandler;
-
- @Getter
- private final ObservableList voteResultExceptions = FXCollections.observableArrayList();
+ private final BlindVoteListService blindVoteListService;
+ private final ProposalService proposalService;
+ private final P2PService p2PService;
@Inject
- public MissingDataRequestService(RepublishGovernanceDataHandler republishGovernanceDataHandler) {
+ public MissingDataRequestService(RepublishGovernanceDataHandler republishGovernanceDataHandler,
+ BlindVoteListService blindVoteListService,
+ ProposalService proposalService,
+ P2PService p2PService) {
this.republishGovernanceDataHandler = republishGovernanceDataHandler;
+ this.blindVoteListService = blindVoteListService;
+ this.proposalService = proposalService;
+ this.p2PService = p2PService;
}
@@ -46,12 +62,6 @@ public class MissingDataRequestService implements DaoSetupService {
@Override
public void addListeners() {
- voteResultExceptions.addListener((ListChangeListener) c -> {
- c.next();
- if (c.wasAdded()) {
- republishGovernanceDataHandler.sendRepublishRequest();
- }
- });
}
@Override
@@ -63,7 +73,45 @@ public class MissingDataRequestService implements DaoSetupService {
// API
///////////////////////////////////////////////////////////////////////////////////////////
- public void addVoteResultException(VoteResultException voteResultException) {
- this.voteResultExceptions.add(voteResultException);
+ public void sendRepublishRequest() {
+ republishGovernanceDataHandler.sendRepublishRequest();
+ }
+
+ public void reRepublishAllGovernanceData() {
+ log.warn("We received a RepublishGovernanceDataRequest and re-published all proposalPayloads and " +
+ "blindVotePayloads to the P2P network.");
+
+ ObservableList proposalPayloads = proposalService.getProposalPayloads();
+ proposalPayloads.forEach(proposalPayload -> {
+ // We want a random delay between 0.1 and 30 sec. depending on the number of items
+ int delay = Math.max(100, Math.min(30_000, new Random().nextInt(proposalPayloads.size() * 500)));
+ UserThread.runAfter(() -> {
+ boolean success = p2PService.addPersistableNetworkPayload(proposalPayload, true);
+ String txId = proposalPayload.getProposal().getTxId();
+ if (success) {
+ log.debug("We received a RepublishGovernanceDataRequest and re-published a proposalPayload to " +
+ "the P2P network as append only data. proposalTxId={}", txId);
+ } else {
+ log.error("Adding of proposalPayload to P2P network failed. proposalTxId={}", txId);
+ }
+ }, delay, TimeUnit.MILLISECONDS);
+ });
+
+ ObservableList blindVotePayloads = blindVoteListService.getBlindVotePayloads();
+ blindVotePayloads
+ .forEach(blindVotePayload -> {
+ // We want a random delay between 0.1 and 30 sec. depending on the number of items
+ int delay = Math.max(100, Math.min(30_000, new Random().nextInt(blindVotePayloads.size() * 500)));
+ UserThread.runAfter(() -> {
+ boolean success = p2PService.addPersistableNetworkPayload(blindVotePayload, true);
+ String txId = blindVotePayload.getBlindVote().getTxId();
+ if (success) {
+ log.debug("We received a RepublishGovernanceDataRequest and re-published a blindVotePayload to " +
+ "the P2P network as append only data. blindVoteTxId={}", txId);
+ } else {
+ log.error("Adding of blindVotePayload to P2P network failed. blindVoteTxId={}", txId);
+ }
+ }, delay, TimeUnit.MILLISECONDS);
+ });
}
}
diff --git a/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultConsensus.java b/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultConsensus.java
index a69b8d98e9..15c50a1b09 100644
--- a/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultConsensus.java
+++ b/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultConsensus.java
@@ -68,27 +68,31 @@ public class VoteResultConsensus {
// hex encoded hashOfProposalList for comparision
@Nullable
public static byte[] getMajorityHash(List hashWithStakeList)
- throws VoteResultException.ConsensusException, VoteResultException.ValidationException {
+ throws VoteResultException.ValidationException, VoteResultException.ConsensusException {
try {
checkArgument(!hashWithStakeList.isEmpty(), "hashWithStakeList must not be empty");
- hashWithStakeList.sort(Comparator.comparingLong(VoteResultService.HashWithStake::getStake).reversed()
- .thenComparing(hashWithStake -> Utilities.encodeToHex(hashWithStake.getHash())));
-
- // If there are conflicting data views (multiple hashes) we only consider the voting round as valid if
- // the majority is a super majority with > 80%.
- if (hashWithStakeList.size() > 1) {
- long stakeOfAll = hashWithStakeList.stream().mapToLong(VoteResultService.HashWithStake::getStake).sum();
- long stakeOfFirst = hashWithStakeList.get(0).getStake();
- if ((double) stakeOfFirst / (double) stakeOfAll < 0.8) {
- throw new VoteResultException.ConsensusException("The winning data view has less then 80% of the " +
- "total stake of all data views. We consider the voting cycle as invalid if the " +
- "winning data view does not reach a super majority.");
- }
- }
- return hashWithStakeList.get(0).getHash();
} catch (Throwable t) {
throw new VoteResultException.ValidationException(t);
}
+
+ hashWithStakeList.sort(Comparator.comparingLong(VoteResultService.HashWithStake::getStake).reversed()
+ .thenComparing(hashWithStake -> Utilities.encodeToHex(hashWithStake.getHash())));
+
+ // If there are conflicting data views (multiple hashes) we only consider the voting round as valid if
+ // the majority is a super majority with > 80%.
+ if (hashWithStakeList.size() > 1) {
+ long stakeOfAll = hashWithStakeList.stream().mapToLong(VoteResultService.HashWithStake::getStake).sum();
+ long stakeOfFirst = hashWithStakeList.get(0).getStake();
+ if ((double) stakeOfFirst / (double) stakeOfAll < 0.8) {
+ log.warn("The winning data view has less then 80% of the " +
+ "total stake of all data views. We consider the voting cycle as invalid if the " +
+ "winning data view does not reach a super majority. hashWithStakeList={}", hashWithStakeList);
+ throw new VoteResultException.ConsensusException("The winning data view has less then 80% of the " +
+ "total stake of all data views. We consider the voting cycle as invalid if the " +
+ "winning data view does not reach a super majority.");
+ }
+ }
+ return hashWithStakeList.get(0).getHash();
}
// Key is stored after version and type bytes and list of Blind votes. It has 16 bytes
diff --git a/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultException.java b/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultException.java
index 7e2814b3de..e6f338c1bd 100644
--- a/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultException.java
+++ b/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultException.java
@@ -18,34 +18,37 @@
package bisq.core.dao.governance.voteresult;
import bisq.core.dao.state.model.governance.Ballot;
+import bisq.core.dao.state.model.governance.Cycle;
import java.util.List;
import lombok.EqualsAndHashCode;
+import lombok.Getter;
import lombok.Value;
public class VoteResultException extends Exception {
+ @Getter
+ private final int heightOfFirstBlockInCycle;
- VoteResultException(Throwable cause) {
+ VoteResultException(Cycle cycle, Throwable cause) {
super(cause);
- }
-
- private VoteResultException(String message) {
- super(message);
- }
-
- private VoteResultException(String message, Throwable cause) {
- super(message, cause);
+ this.heightOfFirstBlockInCycle = cycle.getHeightOfFirstBlock();
}
@Override
public String toString() {
return "VoteResultException{" +
+ "\n heightOfFirstBlockInCycle=" + heightOfFirstBlockInCycle +
"\n} " + super.toString();
}
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Static sub classes
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
@EqualsAndHashCode(callSuper = true)
- public static class ConsensusException extends VoteResultException {
+ public static class ConsensusException extends Exception {
ConsensusException(String message) {
super(message);
@@ -59,7 +62,7 @@ public class VoteResultException extends Exception {
}
@EqualsAndHashCode(callSuper = true)
- public static class ValidationException extends VoteResultException {
+ public static class ValidationException extends Exception {
ValidationException(Throwable cause) {
super("Validation of vote result failed.", cause);
@@ -74,7 +77,7 @@ public class VoteResultException extends Exception {
}
@EqualsAndHashCode(callSuper = true)
- public static abstract class MissingDataException extends VoteResultException {
+ public static abstract class MissingDataException extends Exception {
private MissingDataException(String message) {
super(message);
}
@@ -114,7 +117,7 @@ public class VoteResultException extends Exception {
@EqualsAndHashCode(callSuper = true)
@Value
- public static class DecryptionException extends VoteResultException {
+ public static class DecryptionException extends Exception {
public DecryptionException(Throwable cause) {
super(cause);
}
diff --git a/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultService.java b/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultService.java
index c0c8eea28b..309199dad1 100644
--- a/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultService.java
+++ b/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultService.java
@@ -33,12 +33,14 @@ import bisq.core.dao.governance.votereveal.VoteRevealConsensus;
import bisq.core.dao.governance.votereveal.VoteRevealService;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.state.DaoStateService;
+import bisq.core.dao.state.model.blockchain.Block;
import bisq.core.dao.state.model.blockchain.Tx;
import bisq.core.dao.state.model.blockchain.TxOutput;
import bisq.core.dao.state.model.governance.Ballot;
import bisq.core.dao.state.model.governance.BallotList;
import bisq.core.dao.state.model.governance.ChangeParamProposal;
import bisq.core.dao.state.model.governance.ConfiscateBondProposal;
+import bisq.core.dao.state.model.governance.Cycle;
import bisq.core.dao.state.model.governance.DaoPhase;
import bisq.core.dao.state.model.governance.DecryptedBallotsWithMerits;
import bisq.core.dao.state.model.governance.EvaluatedProposal;
@@ -55,6 +57,7 @@ import bisq.core.locale.CurrencyUtil;
import bisq.network.p2p.storage.P2PDataStorage;
import bisq.common.util.MathUtils;
+import bisq.common.util.PermutationUtil;
import bisq.common.util.Utilities;
import javax.inject.Inject;
@@ -140,7 +143,6 @@ public class VoteResultService implements DaoStateListener, DaoSetupService {
@Override
public void start() {
- maybeCalculateVoteResult(daoStateService.getChainHeight());
}
@@ -150,14 +152,17 @@ public class VoteResultService implements DaoStateListener, DaoSetupService {
@Override
public void onNewBlockHeight(int blockHeight) {
- // TODO check if we should use onParseTxsComplete for calling maybeCalculateVoteResult
- maybeCalculateVoteResult(blockHeight);
}
@Override
public void onParseBlockChainComplete() {
}
+ @Override
+ public void onParseTxsComplete(Block block) {
+ maybeCalculateVoteResult(block.getHeight());
+ }
+
///////////////////////////////////////////////////////////////////////////////////////////
// Private
@@ -165,6 +170,7 @@ public class VoteResultService implements DaoStateListener, DaoSetupService {
private void maybeCalculateVoteResult(int chainHeight) {
if (isInVoteResultPhase(chainHeight)) {
+ Cycle currentCycle = periodService.getCurrentCycle();
long startTs = System.currentTimeMillis();
Set decryptedBallotsWithMeritsSet = getDecryptedBallotsWithMeritsSet(chainHeight);
decryptedBallotsWithMeritsSet.stream()
@@ -207,19 +213,19 @@ public class VoteResultService implements DaoStateListener, DaoSetupService {
}
} catch (VoteResultException.ValidationException e) {
- log.error(e.toString());
+ log.warn(e.toString());
e.printStackTrace();
- voteResultExceptions.add(e);
+ voteResultExceptions.add(new VoteResultException(currentCycle, e));
} catch (VoteResultException.ConsensusException e) {
- log.error(e.toString());
- log.error("decryptedBallotsWithMeritsSet " + decryptedBallotsWithMeritsSet);
+ log.warn(e.toString());
+ log.warn("decryptedBallotsWithMeritsSet " + decryptedBallotsWithMeritsSet);
e.printStackTrace();
//TODO notify application of that case (e.g. add error handler)
// The vote cycle is invalid as conflicting data views of the blind vote data exist and the winner
// did not reach super majority of 80%.
- voteResultExceptions.add(e);
+ voteResultExceptions.add(new VoteResultException(currentCycle, e));
}
} else {
log.info("There have not been any votes in that cycle. chainHeight={}", chainHeight);
@@ -256,6 +262,7 @@ public class VoteResultService implements DaoStateListener, DaoSetupService {
return null;
}
+ Cycle currentCycle = periodService.getCurrentCycle();
try {
// TODO maybe verify version in opReturn
@@ -291,12 +298,12 @@ public class VoteResultService implements DaoStateListener, DaoSetupService {
return new DecryptedBallotsWithMerits(hashOfBlindVoteList, blindVoteTxId, voteRevealTxId, blindVoteStake, ballotList, meritList);
} catch (VoteResultException.MissingBallotException missingBallotException) {
log.warn("We are missing proposals to create the vote result: " + missingBallotException.toString());
- missingDataRequestService.addVoteResultException(missingBallotException);
- voteResultExceptions.add(missingBallotException);
+ missingDataRequestService.sendRepublishRequest();
+ voteResultExceptions.add(new VoteResultException(currentCycle, missingBallotException));
return null;
} catch (VoteResultException.DecryptionException decryptionException) {
- log.error("Could not decrypt data: " + decryptionException.toString());
- voteResultExceptions.add(decryptionException);
+ log.warn("Could not decrypt data: " + decryptionException.toString());
+ voteResultExceptions.add(new VoteResultException(currentCycle, decryptionException));
return null;
}
} else {
@@ -306,17 +313,17 @@ public class VoteResultService implements DaoStateListener, DaoSetupService {
"recover the missing blind vote by a request to our peers. blindVoteTxId={}", blindVoteTxId);
VoteResultException.MissingBlindVoteDataException voteResultException = new VoteResultException.MissingBlindVoteDataException(blindVoteTxId);
- missingDataRequestService.addVoteResultException(voteResultException);
- voteResultExceptions.add(voteResultException);
+ missingDataRequestService.sendRepublishRequest();
+ voteResultExceptions.add(new VoteResultException(currentCycle, voteResultException));
return null;
}
} catch (VoteResultException.ValidationException e) {
- log.error("Could not create DecryptedBallotsWithMerits because of voteResultValidationException: " + e.toString());
- voteResultExceptions.add(e);
+ log.warn("Could not create DecryptedBallotsWithMerits because of voteResultValidationException: " + e.toString());
+ voteResultExceptions.add(new VoteResultException(currentCycle, e));
return null;
} catch (Throwable e) {
log.error("Could not create DecryptedBallotsWithMerits because of an unknown exception: " + e.toString());
- voteResultExceptions.add(new VoteResultException(e));
+ voteResultExceptions.add(new VoteResultException(currentCycle, e));
return null;
}
})
@@ -414,28 +421,38 @@ public class VoteResultService implements DaoStateListener, DaoSetupService {
// It still could be that we have additional blind votes so our hash does not match. We can try to permute
// our list with excluding items to see if we get a matching list. If not last resort is to request the
// missing items from the network.
- List permutatedListMatchingMajority = findPermutatedListMatchingMajority(majorityVoteListHash);
- if (!permutatedListMatchingMajority.isEmpty()) {
- log.info("We found a permutation of our blindVote list which matches the majority view. " +
- "permutatedListMatchingMajority={}", permutatedListMatchingMajority);
+ Optional> permutatedList = findPermutatedListMatchingMajority(majorityVoteListHash);
+ if (permutatedList.isPresent()) {
//TODO do we need to apply/store it for later use?
+
+ return true;
} else {
- log.info("We did not find a permutation of our blindVote list which matches the majority view. " +
+ log.warn("We did not find a permutation of our blindVote list which matches the majority view. " +
"We will request the blindVote data from the peers.");
// This is async operation. We will restart the whole verification process once we received the data.
- requestBlindVoteListFromNetwork(majorityVoteListHash);
+ missingDataRequestService.sendRepublishRequest();
}
}
return matches;
}
- private List findPermutatedListMatchingMajority(byte[] majorityVoteListHash) {
+ private Optional> findPermutatedListMatchingMajority(byte[] majorityVoteListHash) {
List list = BlindVoteConsensus.getSortedBlindVoteListOfCycle(blindVoteListService);
- while (!list.isEmpty() && !isListMatchingMajority(majorityVoteListHash, list)) {
- // We remove first item as it will be sorted anyway...
- list.remove(0);
+ long ts = System.currentTimeMillis();
+ List> result = PermutationUtil.findAllPermutations(list, 1000000);
+ for (List variation : result) {
+ if (isListMatchingMajority(majorityVoteListHash, variation)) {
+ log.info("We found a variation of the blind vote list which matches the majority hash. variation={}",
+ variation);
+ log.info("findPermutatedListMatchingMajority for {} items took {} ms.",
+ list.size(), (System.currentTimeMillis() - ts));
+ return Optional.of(variation);
+ }
}
- return list;
+ log.info("We did not find a variation of the blind vote list which matches the majority hash.");
+ log.info("findPermutatedListMatchingMajority for {} items took {} ms.",
+ list.size(), (System.currentTimeMillis() - ts));
+ return Optional.empty();
}
private boolean isListMatchingMajority(byte[] majorityVoteListHash, List list) {
@@ -443,10 +460,6 @@ public class VoteResultService implements DaoStateListener, DaoSetupService {
return Arrays.equals(majorityVoteListHash, hashOfBlindVoteList);
}
- private void requestBlindVoteListFromNetwork(byte[] majorityVoteListHash) {
- //TODO impl
- }
-
private Set getEvaluatedProposals(Set decryptedBallotsWithMeritsSet, int chainHeight) {
// We reorganize the data structure to have a map of proposals with a list of VoteWithStake objects
Map> resultListByProposalMap = getVoteWithStakeListByProposalMap(decryptedBallotsWithMeritsSet);
diff --git a/core/src/main/java/bisq/core/dao/governance/votereveal/VoteRevealService.java b/core/src/main/java/bisq/core/dao/governance/votereveal/VoteRevealService.java
index 5a2aea27c5..fd16b89859 100644
--- a/core/src/main/java/bisq/core/dao/governance/votereveal/VoteRevealService.java
+++ b/core/src/main/java/bisq/core/dao/governance/votereveal/VoteRevealService.java
@@ -36,6 +36,7 @@ import bisq.core.dao.node.BsqNode;
import bisq.core.dao.node.BsqNodeProvider;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.state.DaoStateService;
+import bisq.core.dao.state.model.blockchain.Block;
import bisq.core.dao.state.model.blockchain.TxOutput;
import bisq.core.dao.state.model.governance.DaoPhase;
@@ -54,6 +55,7 @@ import javafx.collections.ObservableList;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
@@ -73,6 +75,11 @@ import lombok.extern.slf4j.Slf4j;
*/
@Slf4j
public class VoteRevealService implements DaoStateListener, DaoSetupService {
+
+ public interface VoteRevealTxPublishedListener {
+ void onVoteRevealTxPublished(String txId);
+ }
+
private final DaoStateService daoStateService;
private final BlindVoteListService blindVoteListService;
private final PeriodService periodService;
@@ -86,7 +93,7 @@ public class VoteRevealService implements DaoStateListener, DaoSetupService {
@Getter
private final ObservableList voteRevealExceptions = FXCollections.observableArrayList();
private final BsqNode bsqNode;
-
+ private final List voteRevealTxPublishedListeners = new ArrayList<>();
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
@@ -131,7 +138,6 @@ public class VoteRevealService implements DaoStateListener, DaoSetupService {
@Override
public void start() {
- maybeRevealVotes();
}
@@ -141,7 +147,14 @@ public class VoteRevealService implements DaoStateListener, DaoSetupService {
public byte[] getHashOfBlindVoteList() {
List blindVotes = BlindVoteConsensus.getSortedBlindVoteListOfCycle(blindVoteListService);
- return VoteRevealConsensus.getHashOfBlindVoteList(blindVotes);
+ byte[] hashOfBlindVoteList = VoteRevealConsensus.getHashOfBlindVoteList(blindVotes);
+ log.info("blindVoteList for creating hash: " + blindVotes);
+ log.info("Sha256Ripemd160 hash of hashOfBlindVoteList " + Utilities.bytesAsHexString(hashOfBlindVoteList));
+ return hashOfBlindVoteList;
+ }
+
+ public void addVoteRevealTxPublishedListener(VoteRevealTxPublishedListener voteRevealTxPublishedListener) {
+ voteRevealTxPublishedListeners.add(voteRevealTxPublishedListener);
}
@@ -151,40 +164,43 @@ public class VoteRevealService implements DaoStateListener, DaoSetupService {
@Override
public void onNewBlockHeight(int blockHeight) {
- // TODO check if we should use onParseTxsComplete for calling maybeCalculateVoteResult
-
- maybeRevealVotes();
}
@Override
public void onParseBlockChainComplete() {
}
+ @Override
+ public void onParseTxsCompleteAfterBatchProcessing(Block block) {
+ maybeRevealVotes(block.getHeight());
+ }
+
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
// Creation of vote reveal tx is done without user activity!
- // We create automatically the vote reveal tx when we enter the reveal phase of the current cycle when
+ // We create automatically the vote reveal tx when we are in the reveal phase of the current cycle when
// the blind vote was created in case we have not done it already.
// The voter need to be at least once online in the reveal phase when he has a blind vote created,
- // otherwise his vote becomes invalid and his locked stake will get unlocked
- private void maybeRevealVotes() {
- // We must not use daoStateService.getChainHeight() because that gets updated with each parsed block but we
- // only want to publish the vote reveal tx if our current real chain height is matching the cycle and phase and
- // not at any intermediate height during parsing all blocks. The bsqNode knows the latest height from either
- // Bitcoin Core or from the seed node.
- int chainHeight = bsqNode.getChainTipHeight();
+ // otherwise his vote becomes invalid.
+ // In case the user miss the vote reveal phase an (invalid) vote reveal tx will be created the next time the user is
+ // online. That tx only serves the purpose to unlock the stake from the blind vote but it will be ignored for voting.
+ // A blind vote which did not get revealed might still be part of the majority hash calculation as we cannot know
+ // which blind votes might be revealed until the phase is over at the moment when we publish the vote reveal tx.
+ private void maybeRevealVotes(int chainHeight) {
myVoteListService.getMyVoteList().stream()
.filter(myVote -> myVote.getRevealTxId() == null) // we have not already revealed
.forEach(myVote -> {
boolean isInVoteRevealPhase = periodService.getPhaseForHeight(chainHeight) == DaoPhase.Phase.VOTE_REVEAL;
boolean isBlindVoteTxInCorrectPhaseAndCycle = periodService.isTxInPhaseAndCycle(myVote.getTxId(), DaoPhase.Phase.BLIND_VOTE, chainHeight);
if (isInVoteRevealPhase && isBlindVoteTxInCorrectPhaseAndCycle) {
+ log.info("We call revealVote at blockHeight {} for blindVoteTxId {}", chainHeight, myVote.getTxId());
// Standard case that we are in the correct phase and cycle and create the reveal tx.
- revealVote(myVote);
+ revealVote(myVote, true);
} else {
+ // We missed the vote reveal phase but publish a vote reveal tx to unlock the blind vote stake.
boolean isAfterVoteRevealPhase = periodService.getPhaseForHeight(chainHeight).ordinal() > DaoPhase.Phase.VOTE_REVEAL.ordinal();
// We missed the reveal phase but we are in the correct cycle
@@ -203,32 +219,30 @@ public class VoteRevealService implements DaoStateListener, DaoSetupService {
// As this is an exceptional case we prefer to have a simple solution instead and just
// publish the vote reveal tx but are aware that is is invalid.
log.warn("We missed the vote reveal phase but publish now the tx to unlock our locked " +
- "BSQ from the blind vote tx. BlindVoteTxId={}", myVote.getTxId());
+ "BSQ from the blind vote tx. BlindVoteTxId={}, blockHeight={}",
+ myVote.getTxId(), chainHeight);
// We handle the exception here inside the stream iteration as we have not get triggered from an
// outside user intent anyway. We keep errors in a observable list so clients can observe that to
// get notified if anything went wrong.
- revealVote(myVote);
+ revealVote(myVote, false);
}
}
});
}
- private void revealVote(MyVote myVote) {
+ private void revealVote(MyVote myVote, boolean inBlindVotePhase) {
try {
// We collect all valid blind vote items we received via the p2p network.
// It might be that different nodes have a different collection of those items.
// To ensure we get a consensus of the data for later calculating the result we will put a hash of each
- // voters blind vote collection into the opReturn data and check for a majority at issuance time.
+ // voter's blind vote collection into the opReturn data and check for a majority in the vote result phase.
// The voters "vote" with their stake at the reveal tx for their version of the blind vote collection.
- // TODO make more clear by using param like here:
- /* List blindVotes = BlindVoteConsensus.getSortedBlindVoteListOfCycle(blindVoteListService);
- VoteRevealConsensus.getHashOfBlindVoteList(blindVotes);*/
-
- byte[] hashOfBlindVoteList = getHashOfBlindVoteList();
-
- log.info("Sha256Ripemd160 hash of hashOfBlindVoteList " + Utilities.bytesAsHexString(hashOfBlindVoteList));
+ // If we are not in the right phase we just add an empty hash (still need to have the hash as otherwise we
+ // would not recognize the tx as vote reveal tx)
+ byte[] hashOfBlindVoteList = inBlindVotePhase ? getHashOfBlindVoteList() : new byte[20];
+ log.info("revealVote: Sha256Ripemd160 hash of hashOfBlindVoteList " + Utilities.bytesAsHexString(hashOfBlindVoteList));
byte[] opReturnData = VoteRevealConsensus.getOpReturnData(hashOfBlindVoteList, myVote.getSecretKey());
// We search for my unspent stake output.
@@ -245,14 +259,15 @@ public class VoteRevealService implements DaoStateListener, DaoSetupService {
log.info("voteRevealTx={}", voteRevealTx);
publishTx(voteRevealTx);
- // TODO add comment...
// We don't want to wait for a successful broadcast to avoid issues if the broadcast succeeds delayed or at
// next startup but the tx was actually broadcasted.
myVoteListService.applyRevealTxId(myVote, voteRevealTx.getHashAsString());
- // Just for additional resilience we republish our blind votes
- List sortedBlindVoteListOfCycle = BlindVoteConsensus.getSortedBlindVoteListOfCycle(blindVoteListService);
- rePublishBlindVotePayloadList(sortedBlindVoteListOfCycle);
+ if (inBlindVotePhase) {
+ // Just for additional resilience we republish our blind votes
+ List sortedBlindVoteListOfCycle = BlindVoteConsensus.getSortedBlindVoteListOfCycle(blindVoteListService);
+ rePublishBlindVotePayloadList(sortedBlindVoteListOfCycle);
+ }
} catch (IOException | WalletException | TransactionVerificationException
| InsufficientMoneyException e) {
voteRevealExceptions.add(new VoteRevealException("Exception at calling revealVote.",
@@ -267,6 +282,7 @@ public class VoteRevealService implements DaoStateListener, DaoSetupService {
@Override
public void onSuccess(Transaction transaction) {
log.info("voteRevealTx successfully broadcasted.");
+ voteRevealTxPublishedListeners.forEach(l -> l.onVoteRevealTxPublished(transaction.getHashAsString()));
}
@Override
diff --git a/core/src/main/java/bisq/core/dao/node/BsqNode.java b/core/src/main/java/bisq/core/dao/node/BsqNode.java
index 160ec4438b..4ac7980921 100644
--- a/core/src/main/java/bisq/core/dao/node/BsqNode.java
+++ b/core/src/main/java/bisq/core/dao/node/BsqNode.java
@@ -63,7 +63,11 @@ public abstract class BsqNode implements DaoSetupService {
@Nullable
protected Consumer warnMessageHandler;
protected List pendingBlocks = new ArrayList<>();
+
// The chain height of the latest Block we either get reported by Bitcoin Core or from the seed node
+ // This property should not be used in consensus code but only for retrieving blocks as it is not in sync with the
+ // parsing and the daoState. It also does not represent the latest blockHeight but the currently received
+ // (not parsed) block.
@Getter
protected int chainTipHeight;
diff --git a/core/src/main/java/bisq/core/dao/node/full/network/FullNodeNetworkService.java b/core/src/main/java/bisq/core/dao/node/full/network/FullNodeNetworkService.java
index 64afc87751..1a6618ca8f 100644
--- a/core/src/main/java/bisq/core/dao/node/full/network/FullNodeNetworkService.java
+++ b/core/src/main/java/bisq/core/dao/node/full/network/FullNodeNetworkService.java
@@ -17,18 +17,14 @@
package bisq.core.dao.node.full.network;
-import bisq.core.dao.governance.blindvote.BlindVoteListService;
import bisq.core.dao.governance.blindvote.network.messages.RepublishGovernanceDataRequest;
-import bisq.core.dao.governance.blindvote.storage.BlindVotePayload;
-import bisq.core.dao.governance.proposal.ProposalService;
-import bisq.core.dao.governance.proposal.storage.appendonly.ProposalPayload;
+import bisq.core.dao.governance.voteresult.MissingDataRequestService;
import bisq.core.dao.node.full.RawBlock;
import bisq.core.dao.node.messages.GetBlocksRequest;
import bisq.core.dao.node.messages.NewBlockBroadcastMessage;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.model.blockchain.Block;
-import bisq.network.p2p.P2PService;
import bisq.network.p2p.network.Connection;
import bisq.network.p2p.network.MessageListener;
import bisq.network.p2p.network.NetworkNode;
@@ -41,12 +37,8 @@ import bisq.common.proto.network.NetworkEnvelope;
import javax.inject.Inject;
-import javafx.collections.ObservableList;
-
import java.util.HashMap;
import java.util.Map;
-import java.util.Random;
-import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
@@ -68,9 +60,7 @@ public class FullNodeNetworkService implements MessageListener, PeerManager.List
private final NetworkNode networkNode;
private final PeerManager peerManager;
private final Broadcaster broadcaster;
- private final BlindVoteListService blindVoteListService;
- private final ProposalService proposalService;
- private final P2PService p2PService;
+ private final MissingDataRequestService missingDataRequestService;
private final DaoStateService daoStateService;
// Key is connection UID
@@ -86,16 +76,12 @@ public class FullNodeNetworkService implements MessageListener, PeerManager.List
public FullNodeNetworkService(NetworkNode networkNode,
PeerManager peerManager,
Broadcaster broadcaster,
- BlindVoteListService blindVoteListService,
- ProposalService proposalService,
- P2PService p2PService,
+ MissingDataRequestService missingDataRequestService,
DaoStateService daoStateService) {
this.networkNode = networkNode;
this.peerManager = peerManager;
this.broadcaster = broadcaster;
- this.blindVoteListService = blindVoteListService;
- this.proposalService = proposalService;
- this.p2PService = p2PService;
+ this.missingDataRequestService = missingDataRequestService;
this.daoStateService = daoStateService;
}
@@ -195,38 +181,7 @@ public class FullNodeNetworkService implements MessageListener, PeerManager.List
log.warn("We have stopped already. We ignore that onMessage call.");
}
} else if (networkEnvelope instanceof RepublishGovernanceDataRequest) {
- ObservableList blindVotePayloads = blindVoteListService.getBlindVotePayloads();
- blindVotePayloads
- .forEach(blindVotePayload -> {
- // We want a random delay between 0.1 and 30 sec. depending on the number of items
- int delay = Math.max(100, Math.min(30_000, new Random().nextInt(blindVotePayloads.size() * 500)));
- UserThread.runAfter(() -> {
- boolean success = p2PService.addPersistableNetworkPayload(blindVotePayload, true);
- String txId = blindVotePayload.getBlindVote().getTxId();
- if (success) {
- log.warn("We received a RepublishGovernanceDataRequest and re-published a blindVotePayload to " +
- "the P2P network as append only data. blindVoteTxId={}", txId);
- } else {
- log.error("Adding of blindVotePayload to P2P network failed. blindVoteTxId={}", txId);
- }
- }, delay, TimeUnit.MILLISECONDS);
- });
-
- ObservableList proposalPayloads = proposalService.getProposalPayloads();
- proposalPayloads.forEach(proposalPayload -> {
- // We want a random delay between 0.1 and 30 sec. depending on the number of items
- int delay = Math.max(100, Math.min(30_000, new Random().nextInt(proposalPayloads.size() * 500)));
- UserThread.runAfter(() -> {
- boolean success = p2PService.addPersistableNetworkPayload(proposalPayload, true);
- String txId = proposalPayload.getProposal().getTxId();
- if (success) {
- log.warn("We received a RepublishGovernanceDataRequest and re-published a proposalPayload to " +
- "the P2P network as append only data. proposalTxId={}", txId);
- } else {
- log.error("Adding of proposalPayload to P2P network failed. proposalTxId={}", txId);
- }
- }, delay, TimeUnit.MILLISECONDS);
- });
+ missingDataRequestService.reRepublishAllGovernanceData();
}
}
}
diff --git a/core/src/main/java/bisq/core/util/BsqFormatter.java b/core/src/main/java/bisq/core/util/BsqFormatter.java
index f1e671db1f..92a91b3cf5 100644
--- a/core/src/main/java/bisq/core/util/BsqFormatter.java
+++ b/core/src/main/java/bisq/core/util/BsqFormatter.java
@@ -20,6 +20,7 @@ package bisq.core.util;
import bisq.core.app.BisqEnvironment;
import bisq.core.dao.exceptions.ValidationException;
import bisq.core.dao.governance.param.Param;
+import bisq.core.locale.GlobalSettings;
import bisq.core.locale.Res;
import bisq.core.provider.price.MarketPrice;
import bisq.core.util.validation.BtcAddressValidator;
@@ -36,6 +37,9 @@ import org.bitcoinj.utils.MonetaryFormat;
import javax.inject.Inject;
import java.text.DecimalFormat;
+import java.text.NumberFormat;
+
+import java.util.Locale;
import lombok.extern.slf4j.Slf4j;
@@ -44,14 +48,17 @@ public class BsqFormatter extends BSFormatter {
@SuppressWarnings("PointlessBooleanExpression")
private static final boolean useBsqAddressFormat = true || !DevEnv.isDevMode();
private final String prefix = "B";
- private final DecimalFormat amountFormat = new DecimalFormat("###,###,###.##");
- private final DecimalFormat marketCapFormat = new DecimalFormat("###,###,###");
+ private DecimalFormat amountFormat;
+ private DecimalFormat marketCapFormat;
private final MonetaryFormat btcCoinFormat;
@Inject
public BsqFormatter() {
super();
+ GlobalSettings.localeProperty().addListener((observable, oldValue, newValue) -> setFormatter(newValue));
+ setFormatter(GlobalSettings.getLocale());
+
btcCoinFormat = super.coinFormat;
final String baseCurrencyCode = BisqEnvironment.getBaseCurrencyNetwork().getCurrencyCode();
@@ -73,6 +80,16 @@ public class BsqFormatter extends BSFormatter {
amountFormat.setMinimumFractionDigits(2);
}
+ private void setFormatter(Locale locale) {
+ amountFormat = (DecimalFormat) NumberFormat.getNumberInstance(locale);
+ amountFormat.setMinimumFractionDigits(2);
+ amountFormat.setMaximumFractionDigits(2);
+
+ marketCapFormat = (DecimalFormat) NumberFormat.getNumberInstance(locale);
+ marketCapFormat = new DecimalFormat();
+ marketCapFormat.setMaximumFractionDigits(0);
+ }
+
/**
* Returns the base-58 encoded String representation of this
* object, including version and checksum bytes.
diff --git a/core/src/main/java/bisq/core/util/validation/UrlInputValidator.java b/core/src/main/java/bisq/core/util/validation/UrlInputValidator.java
new file mode 100644
index 0000000000..6b088eebf9
--- /dev/null
+++ b/core/src/main/java/bisq/core/util/validation/UrlInputValidator.java
@@ -0,0 +1,45 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.core.util.validation;
+
+import bisq.core.locale.Res;
+
+import java.net.URL;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+public class UrlInputValidator extends InputValidator {
+
+ public UrlInputValidator() {
+ }
+
+ public ValidationResult validate(String input) {
+ ValidationResult validationResult = super.validate(input);
+ if (!validationResult.isValid)
+ return validationResult;
+
+ try {
+ new URL(input); // does not cover all invalid urls, so we use a regex as well
+ String regex = "^(https?)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]";
+ checkArgument(input.matches(regex), "URL does not match regex");
+ return validationResult;
+ } catch (Throwable t) {
+ return new ValidationResult(false, Res.get("validation.invalidUrl"));
+ }
+ }
+}
diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties
index aef07d49dd..1e4e78aa57 100644
--- a/core/src/main/resources/i18n/displayStrings.properties
+++ b/core/src/main/resources/i18n/displayStrings.properties
@@ -1204,6 +1204,10 @@ dao.cycle.voteReveal=Vote reveal phase
dao.cycle.voteResult=Vote result
dao.cycle.phaseDuration={0} blocks (≈{1}); Block {2} - {3} (≈{4} - ≈{5})
+dao.voteReveal.txPublished.headLine=Vote reveal transaction published
+dao.voteReveal.txPublished=Your vote reveal transaction with transaction ID {0} was successfully published.\n\n\
+ This happens automatically by the software if you have participated in the DAO voting.
+
dao.results.cycles.header=Cycles
dao.results.cycles.table.header.cycle=Cycle
dao.results.cycles.table.header.numProposals=Proposals
@@ -1221,6 +1225,7 @@ dao.results.proposals.table.header.result=Vote result
dao.results.proposals.voting.detail.header=Vote results for selected proposal
+dao.results.exceptions=Vote result exception(s)
# suppress inspection "UnusedProperty"
dao.param.UNDEFINED=Undefined
@@ -1438,7 +1443,7 @@ dao.bond.bondedRoleType.MEDIATOR=Mediator
# suppress inspection "UnusedProperty"
dao.bond.bondedRoleType.ARBITRATOR=Arbitrator
-dao.burnBsq.assetFee=Asset listing fee
+dao.burnBsq.assetFee=Asset listing
dao.burnBsq.menuItem.assetFee=Asset listing fee
dao.burnBsq.menuItem.proofOfBurn=Proof of burn
dao.burnBsq.header=Fee for asset listing
@@ -1594,6 +1599,7 @@ dao.proposal.display.myVote.accepted=Accepted
dao.proposal.display.myVote.rejected=Rejected
dao.proposal.display.myVote.ignored=Ignored
dao.proposal.myVote.summary=Voted: {0}; Vote weight: {1} (earned: {2} + stake: {3});
+dao.proposal.myVote.invalid=Vote was invalid
dao.proposal.voteResult.success=Accepted
dao.proposal.voteResult.failed=Rejected
@@ -1608,7 +1614,9 @@ dao.proposal.display.assetComboBox.label=Asset to remove
dao.blindVote=blind vote
dao.blindVote.startPublishing=Publishing blind vote transaction...
-dao.blindVote.success=Your blind vote has been successfully published.
+dao.blindVote.success=Your blind vote transaction has been successfully published.\n\nPlease note, that you have to be \
+ online in the vote reveal phase so that your Bisq application can publish the vote reveal transaction. \
+ Without the vote reveal transaction your vote would be invalid!
dao.wallet.menuItem.send=Send
dao.wallet.menuItem.receive=Receive
@@ -1639,9 +1647,20 @@ dao.wallet.dashboard.burntTx=No. of all fee payments transactions
dao.wallet.dashboard.price=Latest BSQ/BTC trade price (in Bisq)
dao.wallet.dashboard.marketCap=Market capitalisation (based on trade price)
-dao.wallet.receive.fundYourWallet=Fund your BSQ wallet
+dao.wallet.receive.fundYourWallet=Your BSQ receive address
dao.wallet.receive.bsqAddress=BSQ wallet address
+dao.wallet.receive.dao.headline=The Bisq DAO
+dao.wallet.receive.daoInfo=Just as the Bisq exchange is decentralized and censorship-resistant, so is its governance \
+ model — and the Bisq DAO and BSQ token are the tools that make it possible.
+dao.wallet.receive.daoInfo.button=Learn more about the Bisq DAO
+dao.wallet.receive.daoTestnetInfo=The mainnet Bisq DAO is not launched yet but you can learn about the Bisq DAO by \
+ running it on testnet.
+dao.wallet.receive.daoTestnetInfo.button=How to run the Bisq DAO on testnet
+dao.wallet.receive.daoContributorInfo=If you have contributed to Bisq please use the \
+ BSQ address below and make a request for taking part of the BSQ genesis distribution.
+dao.wallet.receive.daoContributorInfo.button=How to be part of the BSQ genesis distribution
+
dao.wallet.send.sendFunds=Send funds
dao.wallet.send.sendBtcFunds=Send non-BSQ funds (BTC)
dao.wallet.send.amount=Amount in BSQ
@@ -1700,7 +1719,12 @@ dao.tx.issuanceFromCompReq.tooltip=Compensation request which led to an issuance
dao.tx.issuanceFromReimbursement=Reimbursement request/issuance
dao.tx.issuanceFromReimbursement.tooltip=Reimbursement request which led to an issuance of new BSQ.\n\
Issuance date: {0}
-dao.proposal.create.missingFunds=You don''t have sufficient funds for creating the proposal.\n\
+dao.proposal.create.missingBsqFunds=You don''t have sufficient BSQ funds for creating the proposal. If you have an \
+ unconfirmed BSQ transaction you need to wait for a blockchain confirmation because BSQ is validated only if it is \
+ included in a block.\n\
+ Missing: {0}
+dao.proposal.create.missingMinerFeeFunds=You don''t have sufficient BTC funds for creating the proposal transaction. \
+ Any BSQ transaction require also a miner fee in BTC.\n\
Missing: {0}
dao.feeTx.confirm=Confirm {0} transaction
dao.feeTx.confirm.details={0} fee: {1}\n\
@@ -2566,3 +2590,4 @@ validation.length=Length must be between {0} and {1}
validation.pattern=Input must be of format: {0}
validation.noHexString=The input is not in HEX format.
validation.advancedCash.invalidFormat=Must be a valid email or wallet id of format: X000000000000
+validation.invalidUrl=This is not a valid URL
diff --git a/core/src/main/resources/i18n/displayStrings_de.properties b/core/src/main/resources/i18n/displayStrings_de.properties
index c757dd4f91..018b607114 100644
--- a/core/src/main/resources/i18n/displayStrings_de.properties
+++ b/core/src/main/resources/i18n/displayStrings_de.properties
@@ -1551,7 +1551,7 @@ dao.tx.issuanceFromCompReq=Entlohnungsanfrage/ausgabe
dao.tx.issuanceFromCompReq.tooltip=Entlohnungsanfrage, die zur Ausgabe neuere BSQ führte.\nAusgabedatum: {0}
dao.tx.issuanceFromReimbursement=Rückerstattungsantrag/Ausgabe
dao.tx.issuanceFromReimbursement.tooltip=Rückerstattungsanfrage, die zur Ausgabe neuer BSQ führte.\nAusgabedatum: {0}
-dao.proposal.create.missingFunds=Sie haben nicht genügend Gelder um den Vorschlag zu erstellen.\nFehlend: {0}
+dao.proposal.create.missingBsqFunds=Sie haben nicht genügend Gelder um den Vorschlag zu erstellen.\nFehlend: {0}
dao.feeTx.confirm=Bestätige {0} Transaktion
dao.feeTx.confirm.details={0} Gebühr: {1}\nMining-Gebühr: {2} ({3} Satoshis/Byte)\nTransaktionsgröße: {4} Kb\n\nSind Sie sicher, dass Sie die {5} Transaktion senden wollen?
diff --git a/core/src/main/resources/i18n/displayStrings_el.properties b/core/src/main/resources/i18n/displayStrings_el.properties
index 0201fe7aad..791fb41f63 100644
--- a/core/src/main/resources/i18n/displayStrings_el.properties
+++ b/core/src/main/resources/i18n/displayStrings_el.properties
@@ -1551,7 +1551,7 @@ dao.tx.issuanceFromCompReq=Αίτημα/έκδοση αποζημίωσης
dao.tx.issuanceFromCompReq.tooltip=Αίτημα αποζημίωσης το οποίο οδήγησε σε έκδοση νέων BSQ.\nΗμερομηνία έκδοσης: {0}
dao.tx.issuanceFromReimbursement=Reimbursement request/issuance
dao.tx.issuanceFromReimbursement.tooltip=Reimbursement request which led to an issuance of new BSQ.\nIssuance date: {0}
-dao.proposal.create.missingFunds=Δεν έχεις επαρκή κεφάλαια για τη δημιουργία της πρότασης.\nΥπολείπονται: {0}
+dao.proposal.create.missingBsqFunds=Δεν έχεις επαρκή κεφάλαια για τη δημιουργία της πρότασης.\nΥπολείπονται: {0}
dao.feeTx.confirm=Επιβεβαίωση συναλλαγής {0}
dao.feeTx.confirm.details={0} fee: {1}\nMining fee: {2} ({3} Satoshis/byte)\nTransaction size: {4} Kb\n\nAre you sure you want to publish the {5} transaction?
diff --git a/core/src/main/resources/i18n/displayStrings_es.properties b/core/src/main/resources/i18n/displayStrings_es.properties
index b7832a6325..9585f8567d 100644
--- a/core/src/main/resources/i18n/displayStrings_es.properties
+++ b/core/src/main/resources/i18n/displayStrings_es.properties
@@ -1551,7 +1551,7 @@ dao.tx.issuanceFromCompReq=Solicitud/emisión de compensación
dao.tx.issuanceFromCompReq.tooltip=Solicitud de compensación que lleva a emitir nuevos BSQ.\nFecha de emisión: {0}
dao.tx.issuanceFromReimbursement=Solicitud de reembolso/emisión
dao.tx.issuanceFromReimbursement.tooltip=Solicitud de reembolso que lleva a una emisión de nuevos BSQ.\nFecha de emisión: {0}
-dao.proposal.create.missingFunds=No tiene suficientes fondos para crear la propuesta.\nFaltan: {0}
+dao.proposal.create.missingBsqFunds=No tiene suficientes fondos para crear la propuesta.\nFaltan: {0}
dao.feeTx.confirm=Confirmar transacción {0}
dao.feeTx.confirm.details={0} tasa: {1}\nTasa de minado: {2} ({3} Satoshis/byte)\nTamaño de la transacción: {4} Kb\n\nEstá seguro de que quire publicar la transacción {5}?
diff --git a/core/src/main/resources/i18n/displayStrings_fa.properties b/core/src/main/resources/i18n/displayStrings_fa.properties
index 2eb7b028bd..672af9be47 100644
--- a/core/src/main/resources/i18n/displayStrings_fa.properties
+++ b/core/src/main/resources/i18n/displayStrings_fa.properties
@@ -1551,7 +1551,7 @@ dao.tx.issuanceFromCompReq=درخواست/صدور خسارت
dao.tx.issuanceFromCompReq.tooltip=درخواست خسارت که منجر به صدور BSQ جدید میشود.\nتاریخ صدور: {0}
dao.tx.issuanceFromReimbursement=درخواست/صدور بازپرداخت
dao.tx.issuanceFromReimbursement.tooltip=درخواست بازپرداختی که منجر به صدور BSQ جدید میشود.\nتاریخ صدور: {0}
-dao.proposal.create.missingFunds=شما وجوه کافی برای ایجاد پیشنهاد را ندارید.\nمقدار مورد نیاز: {0}
+dao.proposal.create.missingBsqFunds=شما وجوه کافی برای ایجاد پیشنهاد را ندارید.\nمقدار مورد نیاز: {0}
dao.feeTx.confirm=تایید {0} تراکنش
dao.feeTx.confirm.details=کارمزد {0}: {1}\nکارمزد استخراج: {2} ({3} ساتوشی بر بایت)\nاندازه تراکنش: {4} Kb\n\nآیا از انتشار تراکنش {5} اطمینان دارید؟
diff --git a/core/src/main/resources/i18n/displayStrings_fr.properties b/core/src/main/resources/i18n/displayStrings_fr.properties
index 5baa7938f9..6ec7a5220e 100644
--- a/core/src/main/resources/i18n/displayStrings_fr.properties
+++ b/core/src/main/resources/i18n/displayStrings_fr.properties
@@ -1551,7 +1551,7 @@ dao.tx.issuanceFromCompReq=Compensation request/issuance
dao.tx.issuanceFromCompReq.tooltip=Compensation request which led to an issuance of new BSQ.\nIssuance date: {0}
dao.tx.issuanceFromReimbursement=Reimbursement request/issuance
dao.tx.issuanceFromReimbursement.tooltip=Reimbursement request which led to an issuance of new BSQ.\nIssuance date: {0}
-dao.proposal.create.missingFunds=You don''t have sufficient funds for creating the proposal.\nMissing: {0}
+dao.proposal.create.missingBsqFunds=You don''t have sufficient funds for creating the proposal.\nMissing: {0}
dao.feeTx.confirm=Confirm {0} transaction
dao.feeTx.confirm.details={0} fee: {1}\nMining fee: {2} ({3} Satoshis/byte)\nTransaction size: {4} Kb\n\nAre you sure you want to publish the {5} transaction?
diff --git a/core/src/main/resources/i18n/displayStrings_hu.properties b/core/src/main/resources/i18n/displayStrings_hu.properties
index 3b232ffb5a..037e9db8af 100644
--- a/core/src/main/resources/i18n/displayStrings_hu.properties
+++ b/core/src/main/resources/i18n/displayStrings_hu.properties
@@ -1551,7 +1551,7 @@ dao.tx.issuanceFromCompReq=Compensation request/issuance
dao.tx.issuanceFromCompReq.tooltip=Compensation request which led to an issuance of new BSQ.\nIssuance date: {0}
dao.tx.issuanceFromReimbursement=Reimbursement request/issuance
dao.tx.issuanceFromReimbursement.tooltip=Reimbursement request which led to an issuance of new BSQ.\nIssuance date: {0}
-dao.proposal.create.missingFunds=Nem rendelkezik elegendő összegekkel a kártérítési kérelem létrehozásához.\nHiányzó: {0}
+dao.proposal.create.missingBsqFunds=Nem rendelkezik elegendő összegekkel a kártérítési kérelem létrehozásához.\nHiányzó: {0}
dao.feeTx.confirm=Confirm {0} transaction
dao.feeTx.confirm.details={0} fee: {1}\nMining fee: {2} ({3} Satoshis/byte)\nTransaction size: {4} Kb\n\nAre you sure you want to publish the {5} transaction?
diff --git a/core/src/main/resources/i18n/displayStrings_pt.properties b/core/src/main/resources/i18n/displayStrings_pt.properties
index 63b928d4f8..2f2fc18169 100644
--- a/core/src/main/resources/i18n/displayStrings_pt.properties
+++ b/core/src/main/resources/i18n/displayStrings_pt.properties
@@ -1551,7 +1551,7 @@ dao.tx.issuanceFromCompReq=Compensation request/issuance
dao.tx.issuanceFromCompReq.tooltip=Compensation request which led to an issuance of new BSQ.\nIssuance date: {0}
dao.tx.issuanceFromReimbursement=Reimbursement request/issuance
dao.tx.issuanceFromReimbursement.tooltip=Reimbursement request which led to an issuance of new BSQ.\nIssuance date: {0}
-dao.proposal.create.missingFunds=Você não tem saldo suficiente para criar a proposta.\nFaltam: {0}
+dao.proposal.create.missingBsqFunds=Você não tem saldo suficiente para criar a proposta.\nFaltam: {0}
dao.feeTx.confirm=Confirmar transação {0}
dao.feeTx.confirm.details=Taxa de {0}: {1}\nTaxa de mineração: {2} ({3} satoshis/byte)\nTamanho da transação: {4} Kb\n\nTem certeza de que deseja publicar a transação {5}?
diff --git a/core/src/main/resources/i18n/displayStrings_ro.properties b/core/src/main/resources/i18n/displayStrings_ro.properties
index 6efcf95dfa..d3183e4b3f 100644
--- a/core/src/main/resources/i18n/displayStrings_ro.properties
+++ b/core/src/main/resources/i18n/displayStrings_ro.properties
@@ -1551,7 +1551,7 @@ dao.tx.issuanceFromCompReq=Compensation request/issuance
dao.tx.issuanceFromCompReq.tooltip=Compensation request which led to an issuance of new BSQ.\nIssuance date: {0}
dao.tx.issuanceFromReimbursement=Reimbursement request/issuance
dao.tx.issuanceFromReimbursement.tooltip=Reimbursement request which led to an issuance of new BSQ.\nIssuance date: {0}
-dao.proposal.create.missingFunds=Nu ai suficiente fonduri pentru crearea solicitării de despăgubire.\nLipsesc: {0}
+dao.proposal.create.missingBsqFunds=Nu ai suficiente fonduri pentru crearea solicitării de despăgubire.\nLipsesc: {0}
dao.feeTx.confirm=Confirm {0} transaction
dao.feeTx.confirm.details={0} fee: {1}\nMining fee: {2} ({3} Satoshis/byte)\nTransaction size: {4} Kb\n\nAre you sure you want to publish the {5} transaction?
diff --git a/core/src/main/resources/i18n/displayStrings_ru.properties b/core/src/main/resources/i18n/displayStrings_ru.properties
index 525fe1f430..2335b6b9c4 100644
--- a/core/src/main/resources/i18n/displayStrings_ru.properties
+++ b/core/src/main/resources/i18n/displayStrings_ru.properties
@@ -1551,7 +1551,7 @@ dao.tx.issuanceFromCompReq=Запрос/выдача компенсации
dao.tx.issuanceFromCompReq.tooltip=Запрос компенсации, который привел к выпуску новых BSQ.\nДата выпуска: {0}
dao.tx.issuanceFromReimbursement=Запрос/выдача возмещения
dao.tx.issuanceFromReimbursement.tooltip=Запрос возмещения, который привел к выпуску новых BSQ.\nДата выпуска: {0}
-dao.proposal.create.missingFunds=У Вас недостаточно средств для создания предложения.\nНехватает: {0}
+dao.proposal.create.missingBsqFunds=У Вас недостаточно средств для создания предложения.\nНехватает: {0}
dao.feeTx.confirm=Подтвердить транзакцию {0}
dao.feeTx.confirm.details={0} сбор: {1}\nкомиссия майнера: {2} ({3} сатоши/байт)\nРазмер транзакиции: {4} Кб\n\nДействительно хотите опубликовать транзакцию {5}?
diff --git a/core/src/main/resources/i18n/displayStrings_sr.properties b/core/src/main/resources/i18n/displayStrings_sr.properties
index 6c56e7263b..5fc783659d 100644
--- a/core/src/main/resources/i18n/displayStrings_sr.properties
+++ b/core/src/main/resources/i18n/displayStrings_sr.properties
@@ -1551,7 +1551,7 @@ dao.tx.issuanceFromCompReq=Compensation request/issuance
dao.tx.issuanceFromCompReq.tooltip=Compensation request which led to an issuance of new BSQ.\nIssuance date: {0}
dao.tx.issuanceFromReimbursement=Reimbursement request/issuance
dao.tx.issuanceFromReimbursement.tooltip=Reimbursement request which led to an issuance of new BSQ.\nIssuance date: {0}
-dao.proposal.create.missingFunds=You don''t have sufficient funds for creating the proposal.\nMissing: {0}
+dao.proposal.create.missingBsqFunds=You don''t have sufficient funds for creating the proposal.\nMissing: {0}
dao.feeTx.confirm=Confirm {0} transaction
dao.feeTx.confirm.details={0} fee: {1}\nMining fee: {2} ({3} Satoshis/byte)\nTransaction size: {4} Kb\n\nAre you sure you want to publish the {5} transaction?
diff --git a/core/src/main/resources/i18n/displayStrings_th.properties b/core/src/main/resources/i18n/displayStrings_th.properties
index 209f0b9e46..f9624671b2 100644
--- a/core/src/main/resources/i18n/displayStrings_th.properties
+++ b/core/src/main/resources/i18n/displayStrings_th.properties
@@ -1551,7 +1551,7 @@ dao.tx.issuanceFromCompReq=คำขอหรือการออกค่า
dao.tx.issuanceFromCompReq.tooltip=คำขอค่าสินไหมทดแทน ซึ่งนำไปสู่การออก BSQ ใหม่\nวันที่ออก: {0}
dao.tx.issuanceFromReimbursement=การออกคำสั่ง/การยื่นคำร้องขอการชำระเงินคืน
dao.tx.issuanceFromReimbursement.tooltip=การเรียกร้องขอการชำระเงินคืนซึ่งเป็นคำสั่งภายใต้ BSQ ฉบับใหม่\nวันที่เริ่มทำการ: {0}
-dao.proposal.create.missingFunds=คุณไม่มีเงินเพียงพอสำหรับการสร้างข้อเสนอ\nขาดไป: {0}
+dao.proposal.create.missingBsqFunds=คุณไม่มีเงินเพียงพอสำหรับการสร้างข้อเสนอ\nขาดไป: {0}
dao.feeTx.confirm=ยืนยันการทำรายการ {0}
dao.feeTx.confirm.details={0} ค่าธรรมเนียม: {1}\nค่าธรรมเนียมการขุด: {2} ({3} Satoshis / byte)\nขนาดของธุรกรรม: {4} Kb\n\nคุณแน่ใจหรือไม่ว่าต้องการเผยแพร่ {5} ธุรกรรม?
diff --git a/core/src/main/resources/i18n/displayStrings_vi.properties b/core/src/main/resources/i18n/displayStrings_vi.properties
index 8ae18b0b2b..f1d054acef 100644
--- a/core/src/main/resources/i18n/displayStrings_vi.properties
+++ b/core/src/main/resources/i18n/displayStrings_vi.properties
@@ -1551,7 +1551,7 @@ dao.tx.issuanceFromCompReq=Yêu cầu bồi thường/ban hành
dao.tx.issuanceFromCompReq.tooltip=Yêu cầu bồi thường dẫn đến ban hành BSQ mới.\nNgày ban hành: {0}
dao.tx.issuanceFromReimbursement=Yêu cầu/ Phát hành bồi hoàn
dao.tx.issuanceFromReimbursement.tooltip=Yêu cầu bồi hoàn dẫn đến ban hành BSQ mới.\nNgày ban hành: {0}
-dao.proposal.create.missingFunds=Bạn không có đủ tiền để tạo đề xuất.\nThiếu: {0}
+dao.proposal.create.missingBsqFunds=Bạn không có đủ tiền để tạo đề xuất.\nThiếu: {0}
dao.feeTx.confirm=Xác nhận {0} giao dịch
dao.feeTx.confirm.details={0} phí: {1}\nPhí đào: {2} ({3} Satoshis/byte)\nKích thước giao dịch: {4} Kb\n\nBạn có chắc là muốn công bố giao dịch {5}?
diff --git a/core/src/main/resources/i18n/displayStrings_zh.properties b/core/src/main/resources/i18n/displayStrings_zh.properties
index ccea4cc7c6..865ee58beb 100644
--- a/core/src/main/resources/i18n/displayStrings_zh.properties
+++ b/core/src/main/resources/i18n/displayStrings_zh.properties
@@ -1551,7 +1551,7 @@ dao.tx.issuanceFromCompReq=补偿请求/发行
dao.tx.issuanceFromCompReq.tooltip=导致新BSQ发行的补偿请求\n发行日期: {0}
dao.tx.issuanceFromReimbursement=Reimbursement request/issuance
dao.tx.issuanceFromReimbursement.tooltip=Reimbursement request which led to an issuance of new BSQ.\nIssuance date: {0}
-dao.proposal.create.missingFunds=You don''t have sufficient funds for creating the proposal.\nMissing: {0}
+dao.proposal.create.missingBsqFunds=You don''t have sufficient funds for creating the proposal.\nMissing: {0}
dao.feeTx.confirm=Confirm {0} transaction
dao.feeTx.confirm.details={0} fee: {1}\nMining fee: {2} ({3} Satoshis/byte)\nTransaction size: {4} Kb\n\nAre you sure you want to publish the {5} transaction?
diff --git a/desktop/package/linux/package.sh b/desktop/package/linux/package.sh
old mode 100644
new mode 100755
diff --git a/desktop/package/linux/release.sh b/desktop/package/linux/release.sh
old mode 100644
new mode 100755
diff --git a/desktop/src/main/java/bisq/desktop/app/BisqApp.java b/desktop/src/main/java/bisq/desktop/app/BisqApp.java
index 890847d2a5..1920bd5853 100644
--- a/desktop/src/main/java/bisq/desktop/app/BisqApp.java
+++ b/desktop/src/main/java/bisq/desktop/app/BisqApp.java
@@ -37,6 +37,7 @@ import bisq.core.app.AvoidStandbyModeService;
import bisq.core.app.BisqEnvironment;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.WalletsManager;
+import bisq.core.dao.governance.voteresult.MissingDataRequestService;
import bisq.core.filter.FilterManager;
import bisq.core.locale.Res;
import bisq.core.offer.OpenOfferManager;
@@ -281,6 +282,8 @@ public class BisqApp extends Application implements UncaughtExceptionHandler {
showSendAlertMessagePopup(injector);
} else if (Utilities.isAltOrCtrlPressed(KeyCode.F, keyEvent)) {
showFilterPopup(injector);
+ } else if (Utilities.isAltOrCtrlPressed(KeyCode.UP, keyEvent)) {
+ injector.getInstance(MissingDataRequestService.class).reRepublishAllGovernanceData();
} else if (Utilities.isAltOrCtrlPressed(KeyCode.T, keyEvent)) {
// Toggle between show tor logs and only show warnings. Helpful in case of connection problems
String pattern = "org.berndpruenster.netlayer";
diff --git a/desktop/src/main/java/bisq/desktop/main/MainView.java b/desktop/src/main/java/bisq/desktop/main/MainView.java
index a674acd9f8..82f30d010e 100644
--- a/desktop/src/main/java/bisq/desktop/main/MainView.java
+++ b/desktop/src/main/java/bisq/desktop/main/MainView.java
@@ -39,7 +39,6 @@ import bisq.desktop.main.portfolio.PortfolioView;
import bisq.desktop.main.settings.SettingsView;
import bisq.desktop.util.Transitions;
-import bisq.core.app.BisqEnvironment;
import bisq.core.exceptions.BisqException;
import bisq.core.locale.Res;
import bisq.core.util.BSFormatter;
@@ -184,27 +183,10 @@ public class MainView extends InitializableView {
JFXBadge portfolioButtonWithBadge = new JFXBadge(portfolioButton);
JFXBadge disputesButtonWithBadge = new JFXBadge(disputesButton);
- final Region daoButtonSpacer = getNavigationSpacer();
-
- if (!BisqEnvironment.isDAOActivatedAndBaseCurrencySupportingBsq()) {
- daoButton.setVisible(false);
- daoButton.setManaged(false);
- daoButtonSpacer.setVisible(false);
- daoButtonSpacer.setManaged(false);
- }
-
root.sceneProperty().addListener((observable, oldValue, newValue) -> {
if (newValue != null) {
newValue.addEventHandler(KeyEvent.KEY_RELEASED, keyEvent -> {
- // TODO can be removed once DAO is released
- if (Utilities.isAltOrCtrlPressed(KeyCode.D, keyEvent)) {
- if (BisqEnvironment.getBaseCurrencyNetwork().isBitcoin()) {
- daoButton.setVisible(true);
- daoButton.setManaged(true);
- daoButtonSpacer.setVisible(true);
- daoButtonSpacer.setManaged(true);
- }
- } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT1, keyEvent)) {
+ if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT1, keyEvent)) {
marketButton.fire();
} else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT2, keyEvent)) {
buyButton.fire();
@@ -264,7 +246,7 @@ public class MainView extends InitializableView {
HBox.setHgrow(primaryNav, Priority.SOMETIMES);
HBox secondaryNav = new HBox(disputesButtonWithBadge, getNavigationSpacer(), settingsButton,
- getNavigationSpacer(), accountButton, daoButtonSpacer, daoButton);
+ getNavigationSpacer(), accountButton, getNavigationSpacer(), daoButton);
secondaryNav.getStyleClass().add("nav-secondary");
HBox.setHgrow(secondaryNav, Priority.SOMETIMES);
diff --git a/desktop/src/main/java/bisq/desktop/main/MainViewModel.java b/desktop/src/main/java/bisq/desktop/main/MainViewModel.java
index 92a568c2b7..39a143b35c 100644
--- a/desktop/src/main/java/bisq/desktop/main/MainViewModel.java
+++ b/desktop/src/main/java/bisq/desktop/main/MainViewModel.java
@@ -283,7 +283,7 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupCompleteList
.show();
});
bisqSetup.setVoteResultExceptionHandler(voteResultException -> {
- new Popup<>().error(voteResultException.toString()).show();
+ log.warn(voteResultException.toString());
});
bisqSetup.setChainFileLockedExceptionHandler(msg -> {
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/DaoView.java b/desktop/src/main/java/bisq/desktop/main/dao/DaoView.java
index 40b44d471a..e0343b3ba0 100644
--- a/desktop/src/main/java/bisq/desktop/main/dao/DaoView.java
+++ b/desktop/src/main/java/bisq/desktop/main/dao/DaoView.java
@@ -30,12 +30,12 @@ import bisq.desktop.main.dao.burnbsq.BurnBsqView;
import bisq.desktop.main.dao.governance.GovernanceView;
import bisq.desktop.main.dao.wallet.BsqWalletView;
import bisq.desktop.main.dao.wallet.dashboard.BsqDashboardView;
+import bisq.desktop.main.overlays.popups.Popup;
import bisq.core.app.BisqEnvironment;
+import bisq.core.dao.governance.votereveal.VoteRevealService;
import bisq.core.locale.Res;
-import bisq.common.app.DevEnv;
-
import javax.inject.Inject;
import javafx.fxml.FXML;
@@ -61,9 +61,15 @@ public class DaoView extends ActivatableViewAndModel {
private BsqWalletView bsqWalletView;
@Inject
- private DaoView(CachingViewLoader viewLoader, Navigation navigation) {
+ private DaoView(CachingViewLoader viewLoader, VoteRevealService voteRevealService, Navigation navigation) {
this.viewLoader = viewLoader;
this.navigation = navigation;
+
+ voteRevealService.addVoteRevealTxPublishedListener(txId -> {
+ new Popup<>().headLine(Res.get("dao.voteReveal.txPublished.headLine"))
+ .feedback(Res.get("dao.voteReveal.txPublished", txId))
+ .show();
+ });
}
@Override
@@ -78,12 +84,13 @@ public class DaoView extends ActivatableViewAndModel {
bondingTab.setClosable(false);
burnBsqTab.setClosable(false);
- root.getTabs().addAll(bsqWalletTab, proposalsTab, bondingTab, burnBsqTab);
-
- if (!BisqEnvironment.isDAOActivatedAndBaseCurrencySupportingBsq() || !DevEnv.isDaoPhase2Activated()) {
- bondingTab.setDisable(true);
+ if (!BisqEnvironment.isDAOActivatedAndBaseCurrencySupportingBsq()) {
proposalsTab.setDisable(true);
+ bondingTab.setDisable(true);
burnBsqTab.setDisable(true);
+ root.getTabs().addAll(bsqWalletTab);
+ } else {
+ root.getTabs().addAll(bsqWalletTab, proposalsTab, bondingTab, burnBsqTab);
}
navigationListener = viewPath -> {
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/bonding/bonds/BondListItem.java b/desktop/src/main/java/bisq/desktop/main/dao/bonding/bonds/BondListItem.java
index 235f19322a..7284439937 100644
--- a/desktop/src/main/java/bisq/desktop/main/dao/bonding/bonds/BondListItem.java
+++ b/desktop/src/main/java/bisq/desktop/main/dao/bonding/bonds/BondListItem.java
@@ -44,7 +44,6 @@ class BondListItem {
private final String bondDetails;
private final BondState bondState;
private final String bondStateString;
- private final Date lockupDate;
BondListItem(Bond bond, BsqFormatter bsqFormatter) {
this.bond = bond;
@@ -59,8 +58,7 @@ class BondListItem {
bondDetails = Utilities.bytesAsHexString(bond.getBondedAsset().getHash());
}
lockupTxId = bond.getLockupTxId();
- lockupDate = new Date(bond.getLockupDate());
- lockupDateString = bsqFormatter.formatDateTime(lockupDate);
+ lockupDateString = bond.getLockupDate() > 0 ? bsqFormatter.formatDateTime(new Date(bond.getLockupDate())) : "-";
bondState = bond.getBondState();
bondStateString = Res.get("dao.bond.bondState." + bond.getBondState().name());
}
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 4f140387d2..11331355d6 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
@@ -173,7 +173,7 @@ public class ProofOfBurnView extends ActivatableView implements
if (!DevEnv.isDevMode()) {
GUIUtil.showBsqFeeInfoPopup(amount, miningFee, txSize, bsqFormatter, btcFormatter,
- Res.get("dao.proofOfBurn.amount"), () -> doPublishFeeTx(transaction, preImageAsString));
+ Res.get("dao.proofOfBurn.header"), () -> doPublishFeeTx(transaction, preImageAsString));
} else {
doPublishFeeTx(transaction, preImageAsString);
}
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/governance/PhasesView.java b/desktop/src/main/java/bisq/desktop/main/dao/governance/PhasesView.java
index 14d9dbbb82..8b044b71d7 100644
--- a/desktop/src/main/java/bisq/desktop/main/dao/governance/PhasesView.java
+++ b/desktop/src/main/java/bisq/desktop/main/dao/governance/PhasesView.java
@@ -21,6 +21,7 @@ import bisq.desktop.components.SeparatedPhaseBars;
import bisq.desktop.util.Layout;
import bisq.core.dao.DaoFacade;
+import bisq.core.dao.governance.period.PeriodService;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.state.model.governance.DaoPhase;
import bisq.core.locale.Res;
@@ -31,9 +32,6 @@ import javafx.scene.layout.GridPane;
import javafx.geometry.Insets;
-import org.fxmisc.easybind.EasyBind;
-import org.fxmisc.easybind.Subscription;
-
import java.util.Arrays;
import java.util.List;
@@ -44,13 +42,14 @@ import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
@Slf4j
public class PhasesView implements DaoStateListener {
private final DaoFacade daoFacade;
+ private final PeriodService periodService;
private SeparatedPhaseBars separatedPhaseBars;
private List phaseBarsItems;
- private Subscription phaseSubscription;
@Inject
- private PhasesView(DaoFacade daoFacade) {
+ private PhasesView(DaoFacade daoFacade, PeriodService periodService) {
this.daoFacade = daoFacade;
+ this.periodService = periodService;
}
public int addGroup(GridPane gridPane, int gridRow) {
@@ -65,24 +64,11 @@ public class PhasesView implements DaoStateListener {
public void activate() {
daoFacade.addBsqStateListener(this);
- phaseSubscription = EasyBind.subscribe(daoFacade.phaseProperty(), phase -> {
- phaseBarsItems.forEach(item -> {
- if (item.getPhase() == phase) {
- item.setActive();
- } else {
- item.setInActive();
- }
- });
-
- });
-
applyData(daoFacade.getChainHeight());
}
public void deactivate() {
daoFacade.removeBsqStateListener(this);
-
- phaseSubscription.unsubscribe();
}
@@ -93,6 +79,19 @@ public class PhasesView implements DaoStateListener {
@Override
public void onNewBlockHeight(int height) {
applyData(height);
+
+ phaseBarsItems.forEach(item -> {
+ DaoPhase.Phase phase = item.getPhase();
+ // Last block is considered for the break as we must not publish a tx there (would get confirmed in next
+ // block which would be a break). Only at result phase we don't have that situation ans show the last block
+ // as valid block in the phase.
+ if (periodService.isInPhaseButNotLastBlock(phase) ||
+ (phase == DaoPhase.Phase.RESULT && periodService.isInPhase(height, phase))) {
+ item.setActive();
+ } else {
+ item.setInActive();
+ }
+ });
}
@Override
@@ -131,6 +130,7 @@ public class PhasesView implements DaoStateListener {
} else if (height > lastBlock) {
progress = 1;
}
+
item.getProgressProperty().set(progress);
});
separatedPhaseBars.updateWidth();
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/governance/ProposalDisplay.java b/desktop/src/main/java/bisq/desktop/main/dao/governance/ProposalDisplay.java
index b0a2cbc268..40221e4163 100644
--- a/desktop/src/main/java/bisq/desktop/main/dao/governance/ProposalDisplay.java
+++ b/desktop/src/main/java/bisq/desktop/main/dao/governance/ProposalDisplay.java
@@ -52,6 +52,7 @@ import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
import bisq.core.util.BsqFormatter;
import bisq.core.util.validation.InputValidator;
+import bisq.core.util.validation.UrlInputValidator;
import bisq.asset.Asset;
@@ -202,7 +203,7 @@ public class ProposalDisplay {
Res.get("dao.proposal.display.link"));
linkInputTextField.setPromptText(Res.get("dao.proposal.display.link.prompt"));
if (isMakeProposalScreen)
- linkInputTextField.setValidator(new InputValidator());
+ linkInputTextField.setValidator(new UrlInputValidator());
inputControls.add(linkInputTextField);
Tuple3