Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Permutation fix #4206

Merged
merged 5 commits into from
Apr 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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