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..c42df229fe
--- /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 to 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..d8bd14b238
--- /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 = 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 = 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 = 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 = 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 = indicesToRemove = Arrays.asList(0, 0);
+ expected = 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 = 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 = 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/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 69c20433d7..cb922a2bff 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,16 +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.ObservableList;
+
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
public class MissingDataRequestService implements DaoSetupService {
private final RepublishGovernanceDataHandler republishGovernanceDataHandler;
+ 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;
}
@@ -51,4 +76,39 @@ public class MissingDataRequestService implements DaoSetupService {
public void sendRepublishRequest() {
republishGovernanceDataHandler.sendRepublishRequest();
}
+
+ public void reRepublishAllGovernanceData() {
+ 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);
+ });
+
+ 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);
+ });
+ }
}
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 4a678eaf02..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,7 +68,7 @@ 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");
} catch (Throwable t) {
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 f07ac358b6..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
@@ -28,17 +28,17 @@ import lombok.Value;
public class VoteResultException extends Exception {
@Getter
- private final int heightOfFirstBlock;
+ private final int heightOfFirstBlockInCycle;
VoteResultException(Cycle cycle, Throwable cause) {
super(cause);
- this.heightOfFirstBlock = cycle.getHeightOfFirstBlock();
+ this.heightOfFirstBlockInCycle = cycle.getHeightOfFirstBlock();
}
@Override
public String toString() {
return "VoteResultException{" +
- "\n heightOfFirstBlock=" + heightOfFirstBlock +
+ "\n heightOfFirstBlockInCycle=" + heightOfFirstBlockInCycle +
"\n} " + super.toString();
}
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 2f1d4b65ce..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,6 +33,7 @@ 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;
@@ -56,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;
@@ -141,7 +143,6 @@ public class VoteResultService implements DaoStateListener, DaoSetupService {
@Override
public void start() {
- maybeCalculateVoteResult(daoStateService.getChainHeight());
}
@@ -151,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
@@ -417,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) {
@@ -446,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 d7e34d4b96..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
@@ -147,7 +147,10 @@ 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) {
@@ -239,7 +242,7 @@ public class VoteRevealService implements DaoStateListener, DaoSetupService {
// 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("Sha256Ripemd160 hash of hashOfBlindVoteList " + Utilities.bytesAsHexString(hashOfBlindVoteList));
+ log.info("revealVote: Sha256Ripemd160 hash of hashOfBlindVoteList " + Utilities.bytesAsHexString(hashOfBlindVoteList));
byte[] opReturnData = VoteRevealConsensus.getOpReturnData(hashOfBlindVoteList, myVote.getSecretKey());
// We search for my unspent stake output.
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/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/dao/governance/result/VoteResultView.java b/desktop/src/main/java/bisq/desktop/main/dao/governance/result/VoteResultView.java
index 8586f35237..b4ad2f3051 100644
--- a/desktop/src/main/java/bisq/desktop/main/dao/governance/result/VoteResultView.java
+++ b/desktop/src/main/java/bisq/desktop/main/dao/governance/result/VoteResultView.java
@@ -216,7 +216,7 @@ public class VoteResultView extends ActivatableView implements D
private void maybeShowVoteResultErrors(Cycle cycle) {
List exceptions = voteResultService.getVoteResultExceptions().stream()
- .filter(voteResultException -> cycle.getHeightOfFirstBlock() == voteResultException.getHeightOfFirstBlock())
+ .filter(voteResultException -> cycle.getHeightOfFirstBlock() == voteResultException.getHeightOfFirstBlockInCycle())
.collect(Collectors.toList());
if (!exceptions.isEmpty()) {
TextArea textArea = FormBuilder.addTextArea(root, ++gridRow, "");