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, "");