mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 18:03:12 +01:00
Merge pull request #2363 from ManfredKarrer/dao-fix-vote-result-with-permutated-blindvotelist
Dao: Add handler for republishing all dao governance data, improve vote result handling
This commit is contained in:
commit
774113418a
107
common/src/main/java/bisq/common/util/PermutationUtil.java
Normal file
107
common/src/main/java/bisq/common/util/PermutationUtil.java
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 <T> Type of List items
|
||||
* @return Partial list where items at indices of indicesToRemove have been removed
|
||||
*/
|
||||
public static <T> List<T> getPartialList(List<T> list, List<Integer> indicesToRemove) {
|
||||
List<T> 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 <T> Type of list items
|
||||
* @return List of possible permutations of the original list
|
||||
*/
|
||||
public static <T> List<List<T>> findAllPermutations(List<T> list, int maxIterations) {
|
||||
List<List<T>> 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<List<T>> 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<T> 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;
|
||||
}
|
||||
}
|
432
common/src/test/java/bisq/common/util/PermutationTest.java
Normal file
432
common/src/test/java/bisq/common/util/PermutationTest.java
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String> list = new ArrayList<>(Arrays.asList(blindVote0, blindVote1, blindVote2, blindVote3, blindVote4, blindVote5));
|
||||
List<Integer> indicesToRemove = Arrays.asList(0, 3);
|
||||
List<String> expected = new ArrayList<>(Arrays.asList(blindVote1, blindVote2, blindVote4, blindVote5));
|
||||
List<String> 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<String> list;
|
||||
List<List<String>> expected;
|
||||
List<List<String>> result;
|
||||
List<String> 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()));
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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(),
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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<ProposalPayload> 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<Proposal> getValidatedProposals() {
|
||||
return proposalPayloads.stream()
|
||||
.map(ProposalPayload::getProposal)
|
||||
|
@ -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<ProposalPayload> 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<BlindVotePayload> 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ public class VoteResultConsensus {
|
||||
// hex encoded hashOfProposalList for comparision
|
||||
@Nullable
|
||||
public static byte[] getMajorityHash(List<VoteResultService.HashWithStake> hashWithStakeList)
|
||||
throws VoteResultException.ConsensusException, VoteResultException.ValidationException {
|
||||
throws VoteResultException.ValidationException, VoteResultException.ConsensusException {
|
||||
try {
|
||||
checkArgument(!hashWithStakeList.isEmpty(), "hashWithStakeList must not be empty");
|
||||
} catch (Throwable t) {
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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<BlindVote> permutatedListMatchingMajority = findPermutatedListMatchingMajority(majorityVoteListHash);
|
||||
if (!permutatedListMatchingMajority.isEmpty()) {
|
||||
log.info("We found a permutation of our blindVote list which matches the majority view. " +
|
||||
"permutatedListMatchingMajority={}", permutatedListMatchingMajority);
|
||||
Optional<List<BlindVote>> 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<BlindVote> findPermutatedListMatchingMajority(byte[] majorityVoteListHash) {
|
||||
private Optional<List<BlindVote>> findPermutatedListMatchingMajority(byte[] majorityVoteListHash) {
|
||||
List<BlindVote> 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<List<BlindVote>> result = PermutationUtil.findAllPermutations(list, 1000000);
|
||||
for (List<BlindVote> 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<BlindVote> 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<EvaluatedProposal> getEvaluatedProposals(Set<DecryptedBallotsWithMerits> decryptedBallotsWithMeritsSet, int chainHeight) {
|
||||
// We reorganize the data structure to have a map of proposals with a list of VoteWithStake objects
|
||||
Map<Proposal, List<VoteWithStake>> resultListByProposalMap = getVoteWithStakeListByProposalMap(decryptedBallotsWithMeritsSet);
|
||||
|
@ -147,7 +147,10 @@ public class VoteRevealService implements DaoStateListener, DaoSetupService {
|
||||
|
||||
public byte[] getHashOfBlindVoteList() {
|
||||
List<BlindVote> 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.
|
||||
|
@ -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<BlindVotePayload> 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<ProposalPayload> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
|
@ -216,7 +216,7 @@ public class VoteResultView extends ActivatableView<GridPane, Void> implements D
|
||||
|
||||
private void maybeShowVoteResultErrors(Cycle cycle) {
|
||||
List<VoteResultException> 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, "");
|
||||
|
Loading…
Reference in New Issue
Block a user