Skip to content

Commit

Permalink
Merge pull request #4206 from sqrrm/permutation-fix
Browse files Browse the repository at this point in the history
Permutation fix
  • Loading branch information
ripcurlx authored Apr 29, 2020
2 parents 91ce44a + 92cd7ec commit 5fd05cf
Show file tree
Hide file tree
Showing 10 changed files with 260 additions and 91 deletions.
4 changes: 3 additions & 1 deletion common/src/main/java/bisq/common/storage/FileManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,9 @@ public static void removeAndBackupFile(File dbDir, File storageFile, String file
log.warn("make dir failed");

File corruptedFile = new File(Paths.get(dbDir.getAbsolutePath(), backupFolderName, fileName).toString());
FileUtil.renameFile(storageFile, corruptedFile);
if (storageFile.exists()) {
FileUtil.renameFile(storageFile, corruptedFile);
}
}

synchronized void removeAndBackupFile(String fileName) throws IOException {
Expand Down
68 changes: 68 additions & 0 deletions common/src/main/java/bisq/common/util/PermutationUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;

import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;

@Slf4j
Expand Down Expand Up @@ -53,8 +56,73 @@ public static <T> List<T> getPartialList(List<T> list, List<Integer> indicesToRe
return altered;
}

public static <T, R> List<T> findMatchingPermutation(R targetValue,
List<T> list,
BiFunction<R, List<T>, Boolean> predicate,
int maxIterations) {
if (predicate.apply(targetValue, list)) {
return list;
} else {
return findMatchingPermutation(targetValue,
list,
new ArrayList<>(),
predicate,
new AtomicInteger(maxIterations));
}
}

private static <T, R> List<T> findMatchingPermutation(R targetValue,
List<T> list,
List<List<T>> lists,
BiFunction<R, List<T>, Boolean> predicate,
AtomicInteger maxIterations) {
for (int level = 0; level < list.size(); level++) {
// Test one level at a time
var result = checkLevel(targetValue, list, predicate, level, 0, maxIterations);
if (!result.isEmpty()) {
return result;
}
}

return new ArrayList<>();
}

@NonNull
private static <T, R> List<T> checkLevel(R targetValue,
List<T> previousLevel,
BiFunction<R, List<T>, Boolean> predicate,
int level,
int permutationIndex,
AtomicInteger maxIterations) {
if (previousLevel.size() == 1) {
return new ArrayList<>();
}
for (int i = permutationIndex; i < previousLevel.size(); i++) {
if (maxIterations.get() <= 0) {
return new ArrayList<>();
}
List<T> newList = new ArrayList<>(previousLevel);
newList.remove(i);
if (level == 0) {
maxIterations.decrementAndGet();
// Check all permutations on this level
if (predicate.apply(targetValue, newList)) {
return newList;
}
} else {
// Test next level
var result = checkLevel(targetValue, newList, predicate, level - 1, i, maxIterations);
if (!result.isEmpty()) {
return result;
}
}
}
return new ArrayList<>();
}

//TODO optimize algorithm so that it starts from all objects and goes down instead starting with from the bottom.
// That should help that we are not hitting the iteration limit so easily.

/**
* 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]]
Expand Down
60 changes: 53 additions & 7 deletions common/src/test/java/bisq/common/util/PermutationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.BiFunction;


import org.junit.Test;

Expand All @@ -29,7 +31,7 @@
public class PermutationTest {


@Test
// @Test
public void testGetPartialList() {
String blindVote0 = "blindVote0";
String blindVote1 = "blindVote1";
Expand Down Expand Up @@ -96,6 +98,48 @@ public void testGetPartialList() {
}

@Test
public void testFindMatchingPermutation() {
String a = "A";
String b = "B";
String c = "C";
String d = "D";
String e = "E";
int limit = 1048575;
List<String> result;
List<String> list;
List<String> expected;
BiFunction<String, List<String>, Boolean> predicate = (target, variationList) -> variationList.toString().equals(target);

list = Arrays.asList(a, b, c, d, e);

expected = Arrays.asList(a);
result = PermutationUtil.findMatchingPermutation(expected.toString(), list, predicate, limit);
assertTrue(expected.toString().equals(result.toString()));


expected = Arrays.asList(a, c, e);
result = PermutationUtil.findMatchingPermutation(expected.toString(), list, predicate, limit);
assertTrue(expected.toString().equals(result.toString()));
}

@Test
public void testBreakAtLimit() {
BiFunction<String, List<String>, Boolean> predicate =
(target, variationList) -> variationList.toString().equals(target);
var list = Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o");
var expected = Arrays.asList("b", "g", "m");

// Takes around 32508 tries starting from longer strings
var limit = 100000;
var result = PermutationUtil.findMatchingPermutation(expected.toString(), list, predicate, limit);
assertTrue(expected.toString().equals(result.toString()));
limit = 1000;
result = PermutationUtil.findMatchingPermutation(expected.toString(), list, predicate, limit);
assertTrue(result.isEmpty());
}


// @Test
public void testFindAllPermutations() {
String blindVote0 = "blindVote0";
String blindVote1 = "blindVote1";
Expand All @@ -107,18 +151,20 @@ public void testFindAllPermutations() {
// 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;
// int limit = 1048575;
int limit = 1048575000;
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<>();
/* for (int i = 0; i < 4; i++) {
list.add("blindVote" + i);
}*/

PermutationUtil.findAllPermutations(list, limit);


list = new ArrayList<>();
Expand Down
14 changes: 11 additions & 3 deletions core/src/main/java/bisq/core/dao/DaoFacade.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;

import java.io.File;
import java.io.IOException;

import java.util.ArrayList;
Expand Down Expand Up @@ -517,7 +518,10 @@ public void publishLockupTx(Coin lockupAmount, int lockTime, LockupReason lockup
lockupTxService.publishLockupTx(lockupAmount, lockTime, lockupReason, hash, resultHandler, exceptionHandler);
}

public Tuple2<Coin, Integer> getLockupTxMiningFeeAndTxSize(Coin lockupAmount, int lockTime, LockupReason lockupReason, byte[] hash)
public Tuple2<Coin, Integer> getLockupTxMiningFeeAndTxSize(Coin lockupAmount,
int lockTime,
LockupReason lockupReason,
byte[] hash)
throws InsufficientMoneyException, IOException, TransactionVerificationException, WalletException {
return lockupTxService.getMiningFeeAndTxSize(lockupAmount, lockTime, lockupReason, hash);
}
Expand Down Expand Up @@ -700,8 +704,12 @@ public String getParamValue(Param param, int blockHeight) {
return daoStateService.getParamValue(param, blockHeight);
}

public void resyncDao(Runnable resultHandler) {
daoStateStorageService.resetDaoState(resultHandler);
public void resyncDaoStateFromGenesis(Runnable resultHandler) {
daoStateStorageService.resyncDaoStateFromGenesis(resultHandler);
}

public void resyncDaoStateFromResources(File storageDir) throws IOException {
daoStateStorageService.resyncDaoStateFromResources(storageDir);
}

public boolean isMyRole(Role role) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -486,20 +487,20 @@ private Optional<List<BlindVote>> findBlindVoteListMatchingMajorityHash(byte[] m
private Optional<List<BlindVote>> findPermutatedListMatchingMajority(byte[] majorityVoteListHash) {
List<BlindVote> list = BlindVoteConsensus.getSortedBlindVoteListOfCycle(blindVoteListService);
long ts = System.currentTimeMillis();
List<List<BlindVote>> result = PermutationUtil.findAllPermutations(list, 1000000);
for (List<BlindVote> variation : result) {
if (isListMatchingMajority(majorityVoteListHash, variation, false)) {
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);
}
}
log.info("We did not find a variation of the blind vote list which matches the majority hash.");

BiFunction<byte[], List<BlindVote>, Boolean> predicate = (hash, variation) ->
isListMatchingMajority(hash, variation, false);

List<BlindVote> result = PermutationUtil.findMatchingPermutation(majorityVoteListHash, list, predicate, 1000000);
log.info("findPermutatedListMatchingMajority for {} items took {} ms.",
list.size(), (System.currentTimeMillis() - ts));
return Optional.empty();
if (result.isEmpty()) {
log.info("We did not find a variation of the blind vote list which matches the majority hash.");
return Optional.empty();
} else {
log.info("We found a variation of the blind vote list which matches the majority hash. variation={}", result);
return Optional.of(result);
}
}

private boolean isListMatchingMajority(byte[] majorityVoteListHash, List<BlindVote> list, boolean doLog) {
Expand All @@ -513,7 +514,8 @@ private boolean isListMatchingMajority(byte[] majorityVoteListHash, List<BlindVo
return Arrays.equals(majorityVoteListHash, hashOfBlindVoteList);
}

private Set<EvaluatedProposal> getEvaluatedProposals(Set<DecryptedBallotsWithMerits> decryptedBallotsWithMeritsSet, int chainHeight) {
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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@

import bisq.common.UserThread;
import bisq.common.config.Config;
import bisq.common.storage.FileManager;
import bisq.common.storage.Storage;

import javax.inject.Inject;
import javax.inject.Named;

import java.io.File;
import java.io.IOException;

import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -101,11 +103,33 @@ public LinkedList<DaoStateHash> getPersistedDaoStateHashChain() {
return store.getDaoStateHashChain();
}

public void resetDaoState(Runnable resultHandler) {
public void resyncDaoStateFromGenesis(Runnable resultHandler) {
persist(new DaoState(), new LinkedList<>(), 1);
UserThread.runAfter(resultHandler, 300, TimeUnit.MILLISECONDS);
}

public void resyncDaoStateFromResources(File storageDir) throws IOException {
// We delete all DAO consensus payload data and remove the daoState so it will rebuild from latest
// resource files.
long currentTime = System.currentTimeMillis();
String backupDirName = "out_of_sync_dao_data";
String newFileName = "BlindVoteStore_" + currentTime;
FileManager.removeAndBackupFile(storageDir, new File(storageDir, "BlindVoteStore"), newFileName, backupDirName);

newFileName = "ProposalStore_" + currentTime;
FileManager.removeAndBackupFile(storageDir, new File(storageDir, "ProposalStore"), newFileName, backupDirName);

// We also need to remove ballot list as it contains the proposals as well. It will be recreated at resync
newFileName = "BallotList_" + currentTime;
FileManager.removeAndBackupFile(storageDir, new File(storageDir, "BallotList"), newFileName, backupDirName);

newFileName = "UnconfirmedBsqChangeOutputList_" + currentTime;
FileManager.removeAndBackupFile(storageDir, new File(storageDir, "UnconfirmedBsqChangeOutputList"), newFileName, backupDirName);

newFileName = "DaoStateStore_" + currentTime;
FileManager.removeAndBackupFile(storageDir, new File(storageDir, "DaoStateStore"), newFileName, backupDirName);
}


///////////////////////////////////////////////////////////////////////////////////////////
// Protected
Expand Down
11 changes: 8 additions & 3 deletions core/src/main/resources/i18n/displayStrings.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1074,10 +1074,15 @@ settings.preferences.languageChange=To apply the language change to all screens
settings.preferences.supportLanguageWarning=In case of a dispute, please note that mediation is handled in {0} and arbitration in {1}.
settings.preferences.selectCurrencyNetwork=Select network
setting.preferences.daoOptions=DAO options
setting.preferences.dao.resync.label=Rebuild DAO state from genesis tx
setting.preferences.dao.resync.button=Resync
setting.preferences.dao.resync.popup=After an application restart the Bisq network governance data will be reloaded from \
setting.preferences.dao.resyncFromGenesis.label=Rebuild DAO state from genesis tx
setting.preferences.dao.resyncFromResources.label=Rebuild DAO state from resources
setting.preferences.dao.resyncFromResources.popup=After an application restart the Bisq network governance data will be reloaded from \
the seed nodes and the BSQ consensus state will be rebuilt from the latest resource files.
setting.preferences.dao.resyncFromGenesis.popup=A resync from genesis transaction can take considerable time and CPU \
resources. Are you sure you want to do that? Mostly a resync from latest resource files is sufficient and much faster.\n\n\
If you proceed, after an application restart the Bisq network governance data will be reloaded from \
the seed nodes and the BSQ consensus state will be rebuilt from the genesis transaction.
setting.preferences.dao.resyncFromGenesis.resync=Resync from genesis and shutdown
setting.preferences.dao.isDaoFullNode=Run Bisq as DAO full node
setting.preferences.dao.rpcUser=RPC username
setting.preferences.dao.rpcPw=RPC password
Expand Down
Loading

0 comments on commit 5fd05cf

Please sign in to comment.