Merge branch 'master' of github.com:bisq-network/bisq into update-data-stores-for-v1.3.3

# Conflicts:
#	p2p/src/main/resources/AccountAgeWitnessStore_BTC_MAINNET
#	p2p/src/main/resources/DaoStateStore_BTC_MAINNET
#	p2p/src/main/resources/SignedWitnessStore_BTC_MAINNET
#	p2p/src/main/resources/TradeStatistics2Store_BTC_MAINNET
This commit is contained in:
Christoph Atteneder 2020-04-29 16:35:44 +02:00
commit d9fe059e42
No known key found for this signature in database
GPG key ID: CD5DC1C529CDFD3B
32 changed files with 796 additions and 320000 deletions

3
.gitattributes vendored
View file

@ -1,10 +1,8 @@
# Auto detect text files and normalize line endings to LF
# This will handle all files NOT found below
* text=auto
# These text files should retain Windows line endings (CRLF)
*.bat text eol=crlf
# These binary files should be left untouched
# (binary is a macro for -text -diff)
*.bmp binary
@ -14,3 +12,4 @@
*.jpg binary
*.jpeg binary
*.png binary
p2p/src/main/resources/*BTC_MAINNET filter=lfs diff=lfs merge=lfs -text

View file

@ -3,8 +3,18 @@ jdk:
- openjdk10
- openjdk12
cache:
directories:
- .git/lfs
git:
lfs_skip_smudge: true
install:
- git lfs pull
before_install:
grep -v '^#' assets/src/main/resources/META-INF/services/bisq.asset.Asset | sort --check --dictionary-order --ignore-case
notifications:
slack:
on_success: change

View file

@ -270,6 +270,17 @@ configure(project(':p2p')) {
testAnnotationProcessor "org.projectlombok:lombok:$lombokVersion"
testCompile("org.mockito:mockito-core:$mockitoVersion")
}
processResources.doFirst {
// Sanity check that Git LFS-managed data store files have actually been sync'd.
// If they have not, e.g. because Git LFS is not installed, they will be text files
// containing a sha256 hash of the remote object, indicating we should stop the
// build and inform the user how to fix the problem.
if (file('src/main/resources/ProposalStore_BTC_MAINNET').text.contains("oid sha256:"))
throw new GradleException("p2p data store files have not been synchronized. " +
"To fix this, ensure you have Git LFS installed and run `git lfs pull`. " +
"See docs/build.md for more information.")
}
}
@ -335,7 +346,7 @@ configure(project(':core')) {
}
configure(project(':cli')) {
mainClassName = 'bisq.cli.app.BisqCliMain'
mainClassName = 'bisq.cli.CliMain'
dependencies {
compile project(':proto')

View file

@ -0,0 +1,189 @@
/*
* 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.cli;
import bisq.proto.grpc.GetBalanceGrpc;
import bisq.proto.grpc.GetBalanceRequest;
import bisq.proto.grpc.GetVersionGrpc;
import bisq.proto.grpc.GetVersionRequest;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;
import joptsimple.OptionException;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import java.text.DecimalFormat;
import java.io.IOException;
import java.io.PrintStream;
import java.math.BigDecimal;
import java.util.List;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import static java.lang.System.err;
import static java.lang.System.exit;
import static java.lang.System.out;
/**
* A command-line client for the Bisq gRPC API.
*/
@Slf4j
public class CliMain {
private static final int EXIT_SUCCESS = 0;
private static final int EXIT_FAILURE = 1;
private enum Method {
getversion,
getbalance
}
public static void main(String[] args) {
var parser = new OptionParser();
var helpOpt = parser.accepts("help", "Print this help text")
.forHelp();
var hostOpt = parser.accepts("host", "rpc server hostname or IP")
.withRequiredArg()
.defaultsTo("localhost");
var portOpt = parser.accepts("port", "rpc server port")
.withRequiredArg()
.ofType(Integer.class)
.defaultsTo(9998);
var passwordOpt = parser.accepts("password", "rpc server password")
.withRequiredArg();
OptionSet options = null;
try {
options = parser.parse(args);
} catch (OptionException ex) {
err.println("Error: " + ex.getMessage());
exit(EXIT_FAILURE);
}
if (options.has(helpOpt)) {
printHelp(parser, out);
exit(EXIT_SUCCESS);
}
@SuppressWarnings("unchecked")
var nonOptionArgs = (List<String>) options.nonOptionArguments();
if (nonOptionArgs.isEmpty()) {
printHelp(parser, err);
err.println("Error: no method specified");
exit(EXIT_FAILURE);
}
var methodName = nonOptionArgs.get(0);
Method method = null;
try {
method = Method.valueOf(methodName);
} catch (IllegalArgumentException ex) {
err.printf("Error: '%s' is not a supported method\n", methodName);
exit(EXIT_FAILURE);
}
var host = options.valueOf(hostOpt);
var port = options.valueOf(portOpt);
var password = options.valueOf(passwordOpt);
if (password == null) {
err.println("Error: missing required 'password' option");
exit(EXIT_FAILURE);
}
var credentials = new PasswordCallCredentials(password);
var channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
channel.shutdown().awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException ex) {
ex.printStackTrace(err);
exit(EXIT_FAILURE);
}
}));
try {
switch (method) {
case getversion: {
var stub = GetVersionGrpc.newBlockingStub(channel).withCallCredentials(credentials);
var request = GetVersionRequest.newBuilder().build();
var version = stub.getVersion(request).getVersion();
out.println(version);
exit(EXIT_SUCCESS);
}
case getbalance: {
var stub = GetBalanceGrpc.newBlockingStub(channel).withCallCredentials(credentials);
var request = GetBalanceRequest.newBuilder().build();
var balance = stub.getBalance(request).getBalance();
if (balance == -1) {
err.println("Error: server is still initializing");
exit(EXIT_FAILURE);
}
out.println(formatBalance(balance));
exit(EXIT_SUCCESS);
}
default: {
err.printf("Error: unhandled method '%s'\n", method);
exit(EXIT_FAILURE);
}
}
} catch (StatusRuntimeException ex) {
// This exception is thrown if the client-provided password credentials do not
// match the value set on the server. The actual error message is in a nested
// exception and we clean it up a bit to make it more presentable.
Throwable t = ex.getCause() == null ? ex : ex.getCause();
err.println("Error: " + t.getMessage().replace("UNAUTHENTICATED: ", ""));
exit(EXIT_FAILURE);
}
}
private static void printHelp(OptionParser parser, PrintStream stream) {
try {
stream.println("Bisq RPC Client");
stream.println();
stream.println("Usage: bisq-cli [options] <method>");
stream.println();
parser.printHelpOn(stream);
stream.println();
stream.println("Method Description");
stream.println("------ -----------");
stream.println("getversion Get server version");
stream.println("getbalance Get server wallet balance");
stream.println();
} catch (IOException ex) {
ex.printStackTrace(stream);
}
}
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
private static String formatBalance(long satoshis) {
var btcFormat = new DecimalFormat("###,##0.00000000");
var satoshiDivisor = new BigDecimal(100000000);
return btcFormat.format(BigDecimal.valueOf(satoshis).divide(satoshiDivisor));
}
}

View file

@ -0,0 +1,45 @@
package bisq.cli;
import io.grpc.CallCredentials;
import io.grpc.Metadata;
import io.grpc.Metadata.Key;
import java.util.concurrent.Executor;
import static io.grpc.Metadata.ASCII_STRING_MARSHALLER;
import static io.grpc.Status.UNAUTHENTICATED;
import static java.lang.String.format;
/**
* Sets the {@value PASSWORD_KEY} rpc call header to a given value.
*/
class PasswordCallCredentials extends CallCredentials {
public static final String PASSWORD_KEY = "password";
private final String passwordValue;
public PasswordCallCredentials(String passwordValue) {
if (passwordValue == null)
throw new IllegalArgumentException(format("'%s' value must not be null", PASSWORD_KEY));
this.passwordValue = passwordValue;
}
@Override
public void applyRequestMetadata(RequestInfo requestInfo, Executor appExecutor, MetadataApplier metadataApplier) {
appExecutor.execute(() -> {
try {
var headers = new Metadata();
var passwordKey = Key.of(PASSWORD_KEY, ASCII_STRING_MARSHALLER);
headers.put(passwordKey, passwordValue);
metadataApplier.apply(headers);
} catch (Throwable ex) {
metadataApplier.fail(UNAUTHENTICATED.withCause(ex));
}
});
}
@Override
public void thisUsesUnstableApi() {
}
}

View file

@ -1,116 +0,0 @@
/*
* 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.cli.app;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import java.util.List;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import static bisq.cli.app.CommandParser.GETBALANCE;
import static bisq.cli.app.CommandParser.GETVERSION;
import static bisq.cli.app.CommandParser.HELP;
import static bisq.cli.app.CommandParser.STOPSERVER;
import static java.lang.String.format;
import static java.lang.System.exit;
import static java.lang.System.out;
/**
* gRPC client.
*/
@Slf4j
public class BisqCliMain {
private static final int EXIT_SUCCESS = 0;
private static final int EXIT_FAILURE = 1;
private final ManagedChannel channel;
private final CliCommand cmd;
private final OptionParser parser;
public static void main(String[] args) {
new BisqCliMain("localhost", 9998, args);
}
private BisqCliMain(String host, int port, String[] args) {
// Channels are secure by default (via SSL/TLS); for the example disable TLS to avoid needing certificates.
this(ManagedChannelBuilder.forAddress(host, port).usePlaintext().build());
String command = parseCommand(args);
String result = runCommand(command);
out.println(result);
try {
shutdown(); // Orderly channel shutdown
} catch (InterruptedException ignored) {
}
}
/**
* Construct client for accessing server using the existing channel.
*/
private BisqCliMain(ManagedChannel channel) {
this.channel = channel;
this.cmd = new CliCommand(channel);
this.parser = new CommandParser().configure();
}
private String runCommand(String command) {
final String result;
switch (command) {
case HELP:
CommandParser.printHelp();
exit(EXIT_SUCCESS);
case GETBALANCE:
long satoshis = cmd.getBalance();
result = satoshis == -1 ? "Server initializing..." : cmd.prettyBalance.apply(satoshis);
break;
case GETVERSION:
result = cmd.getVersion();
break;
case STOPSERVER:
cmd.stopServer();
result = "Server stopped";
break;
default:
result = format("Unknown command '%s'", command);
}
return result;
}
private String parseCommand(String[] params) {
OptionSpec<String> nonOptions = parser.nonOptions().ofType(String.class);
OptionSet options = parser.parse(params);
List<String> detectedOptions = nonOptions.values(options);
if (detectedOptions.isEmpty()) {
CommandParser.printHelp();
exit(EXIT_FAILURE);
}
return detectedOptions.get(0);
}
private void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(1, TimeUnit.SECONDS);
exit(EXIT_SUCCESS);
}
}

View file

@ -1,66 +0,0 @@
package bisq.cli.app;
import bisq.proto.grpc.GetBalanceGrpc;
import bisq.proto.grpc.GetBalanceRequest;
import bisq.proto.grpc.GetVersionGrpc;
import bisq.proto.grpc.GetVersionRequest;
import bisq.proto.grpc.StopServerGrpc;
import bisq.proto.grpc.StopServerRequest;
import io.grpc.ManagedChannel;
import io.grpc.StatusRuntimeException;
import java.text.DecimalFormat;
import java.math.BigDecimal;
import java.util.function.Function;
import lombok.extern.slf4j.Slf4j;
@Slf4j
final class CliCommand {
private final GetBalanceGrpc.GetBalanceBlockingStub getBalanceStub;
private final GetVersionGrpc.GetVersionBlockingStub getVersionStub;
private final StopServerGrpc.StopServerBlockingStub stopServerStub;
private final DecimalFormat btcFormat = new DecimalFormat("###,##0.00000000");
private final BigDecimal satoshiDivisor = new BigDecimal(100000000);
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
final Function<Long, String> prettyBalance = (sats) -> btcFormat.format(BigDecimal.valueOf(sats).divide(satoshiDivisor));
CliCommand(ManagedChannel channel) {
getBalanceStub = GetBalanceGrpc.newBlockingStub(channel);
getVersionStub = GetVersionGrpc.newBlockingStub(channel);
stopServerStub = StopServerGrpc.newBlockingStub(channel);
}
String getVersion() {
GetVersionRequest request = GetVersionRequest.newBuilder().build();
try {
return getVersionStub.getVersion(request).getVersion();
} catch (StatusRuntimeException e) {
return "RPC failed: " + e.getStatus();
}
}
long getBalance() {
GetBalanceRequest request = GetBalanceRequest.newBuilder().build();
try {
return getBalanceStub.getBalance(request).getBalance();
} catch (StatusRuntimeException e) {
log.warn("RPC failed: {}", e.getStatus());
return -1;
}
}
void stopServer() {
StopServerRequest request = StopServerRequest.newBuilder().build();
try {
stopServerStub.stopServer(request);
} catch (StatusRuntimeException e) {
log.warn("RPC failed: {}", e.getStatus());
}
}
}

View file

@ -1,27 +0,0 @@
package bisq.cli.app;
import joptsimple.OptionParser;
import static java.lang.System.out;
final class CommandParser {
// Option name constants
static final String HELP = "help";
static final String GETBALANCE = "getbalance";
static final String GETVERSION = "getversion";
static final String STOPSERVER = "stopserver";
OptionParser configure() {
OptionParser parser = new OptionParser();
parser.allowsUnrecognizedOptions();
parser.nonOptions(GETBALANCE).ofType(String.class).describedAs("get btc balance");
parser.nonOptions(GETVERSION).ofType(String.class).describedAs("get bisq version");
return parser;
}
static void printHelp() {
out.println("Usage: bisq-cli getbalance | getversion");
}
}

156
cli/test.sh Executable file
View file

@ -0,0 +1,156 @@
#!/bin/bash
#
# References & examples for expect:
#
# - https://pantz.org/software/expect/expect_examples_and_tips.html
# - https://stackoverflow.com/questions/13982310/else-string-matching-in-expect
# - https://gist.github.com/Fluidbyte/6294378
# - https://www.oreilly.com/library/view/exploring-expect/9781565920903/ch04.html
#
# Prior to running this script, run:
#
# ./bisq-daemon --apiPassword=xyz
#
# The data directory used must contain an unencrypted wallet with a 0 BTC balance
# Ensure project root is the current working directory
cd $(dirname $0)/..
OUTPUT=$(expect -c '
# exp_internal 1
puts "TEST unsupported cmd error"
set expected "Error: '\''bogus'\'' is not a supported method"
spawn ./bisq-cli --password=xyz bogus
expect {
$expected { puts "PASS" }
default {
set results $expect_out(buffer)
puts "FAIL expected = $expected"
puts " actual = $results"
}
}
')
echo "$OUTPUT"
echo "========================================================================"
OUTPUT=$(expect -c '
puts "TEST unrecognized option error"
set expected "Error: bogus is not a recognized option"
spawn ./bisq-cli --bogus getversion
expect {
$expected { puts "PASS" }
default {
set results $expect_out(buffer)
puts "FAIL expected = $expected"
puts " actual = $results"
}
}
')
echo "$OUTPUT"
echo "========================================================================"
OUTPUT=$(expect -c '
# exp_internal 1
puts "TEST missing required password option error"
set expected "Error: missing required '\''password'\'' option"
spawn ./bisq-cli getversion
expect {
$expected { puts "PASS" }
default {
set results $expect_out(buffer)
puts "FAIL expected = $expected"
puts " actual = $results"
}
}
')
echo "$OUTPUT"
echo "========================================================================"
OUTPUT=$(expect -c '
# exp_internal 1
puts "TEST getversion (incorrect password error)"
set expected "Error: incorrect '\''password'\'' rpc header value"
spawn ./bisq-cli --password=bogus getversion
expect {
$expected { puts "PASS\n" }
default {
set results $expect_out(buffer)
puts "FAIL expected = $expected"
puts " actual = $results"
}
}
')
echo "$OUTPUT"
echo "========================================================================"
OUTPUT=$(expect -c '
# exp_internal 1
puts "TEST getversion (password value in quotes)"
set expected "1.3.2"
# Note: have to define quoted argument in a variable as "''value''"
set pwd_in_quotes "''xyz''"
spawn ./bisq-cli --password=$pwd_in_quotes getversion
expect {
$expected { puts "PASS" }
default {
set results $expect_out(buffer)
puts "FAIL expected = $expected"
puts " actual = $results"
}
}
')
echo "$OUTPUT"
echo "========================================================================"
OUTPUT=$(expect -c '
puts "TEST getversion"
set expected "1.3.2"
spawn ./bisq-cli --password=xyz getversion
expect {
$expected { puts "PASS" }
default {
set results $expect_out(buffer)
puts "FAIL expected = $expected"
puts " actual = $results"
}
}
')
echo "$OUTPUT"
echo "========================================================================"
OUTPUT=$(expect -c '
puts "TEST getbalance"
# exp_internal 1
set expected "0.00000000"
spawn ./bisq-cli --password=xyz getbalance
expect {
$expected { puts "PASS" }
default {
set results $expect_out(buffer)
puts "FAIL expected = $expected"
puts " actual = $results"
}
}
')
echo "$OUTPUT"
echo "========================================================================"
OUTPUT=$(expect -c '
puts "TEST running with no options or arguments prints help text"
# exp_internal 1
set expected "Bisq RPC Client"
spawn ./bisq-cli
expect {
$expected { puts "PASS" }
default {
set results $expect_out(buffer)
puts "FAIL expected = $expected"
puts " actual = $results"
}
}
')
echo "$OUTPUT"
echo "========================================================================"
echo "TEST --help option prints help text"
./bisq-cli --help

View file

@ -116,6 +116,8 @@ public class Config {
public static final String DAO_ACTIVATED = "daoActivated";
public static final String DUMP_DELAYED_PAYOUT_TXS = "dumpDelayedPayoutTxs";
public static final String ALLOW_FAULTY_DELAYED_TXS = "allowFaultyDelayedTxs";
public static final String API_PASSWORD = "apiPassword";
public static final String API_PORT = "apiPort";
// Default values for certain options
public static final int UNSPECIFIED_PORT = -1;
@ -140,7 +142,7 @@ public class Config {
public final boolean helpRequested;
public final File configFile;
// Options supported both at the cli and in the config file
// Options supported on cmd line and in the config file
public final String appName;
public final File userDataDir;
public final File appDataDir;
@ -199,6 +201,8 @@ public class Config {
public final long genesisTotalSupply;
public final boolean dumpDelayedPayoutTxs;
public final boolean allowFaultyDelayedTxs;
public final String apiPassword;
public final int apiPort;
// Properties derived from options but not exposed as options themselves
public final File torDir;
@ -206,7 +210,7 @@ public class Config {
public final File storageDir;
public final File keyStorageDir;
// The parser that will be used to parse both cli and config file options
// The parser that will be used to parse both cmd line and config file options
private final OptionParser parser = new OptionParser();
/**
@ -615,6 +619,17 @@ public class Config {
.ofType(boolean.class)
.defaultsTo(false);
ArgumentAcceptingOptionSpec<String> apiPasswordOpt =
parser.accepts(API_PASSWORD, "gRPC API password")
.withRequiredArg()
.defaultsTo("");
ArgumentAcceptingOptionSpec<Integer> apiPortOpt =
parser.accepts(API_PORT, "gRPC API port")
.withRequiredArg()
.ofType(Integer.class)
.defaultsTo(9998);
try {
CompositeOptionSet options = new CompositeOptionSet();
@ -727,6 +742,8 @@ public class Config {
this.daoActivated = options.valueOf(daoActivatedOpt);
this.dumpDelayedPayoutTxs = options.valueOf(dumpDelayedPayoutTxsOpt);
this.allowFaultyDelayedTxs = options.valueOf(allowFaultyDelayedTxsOpt);
this.apiPassword = options.valueOf(apiPasswordOpt);
this.apiPort = options.valueOf(apiPortOpt);
} catch (OptionException ex) {
throw new ConfigException("problem parsing option '%s': %s",
ex.options().get(0),

View file

@ -165,7 +165,9 @@ public class FileManager<T extends PersistableEnvelope> {
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 {

View file

@ -21,7 +21,10 @@ import java.util.ArrayList;
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
@ -53,8 +56,73 @@ public class PermutationUtil {
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]]

View file

@ -21,6 +21,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.BiFunction;
import org.junit.Test;
@ -29,7 +31,7 @@ import static org.junit.Assert.assertTrue;
public class PermutationTest {
@Test
// @Test
public void testGetPartialList() {
String blindVote0 = "blindVote0";
String blindVote1 = "blindVote1";
@ -96,6 +98,48 @@ public class PermutationTest {
}
@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";
@ -107,18 +151,20 @@ public class PermutationTest {
// 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<>();

View file

@ -91,6 +91,7 @@ import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@ -517,7 +518,10 @@ public class DaoFacade implements DaoSetupService {
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);
}
@ -700,8 +704,12 @@ public class DaoFacade implements DaoSetupService {
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) {

View file

@ -77,6 +77,7 @@ import java.util.Map;
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;
@ -486,20 +487,20 @@ public class VoteResultService implements DaoStateListener, DaoSetupService {
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) {
@ -513,7 +514,8 @@ public class VoteResultService implements DaoStateListener, DaoSetupService {
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);

View file

@ -26,12 +26,14 @@ import bisq.network.p2p.storage.persistence.StoreService;
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;
@ -101,11 +103,33 @@ public class DaoStateStorageService extends StoreService<DaoStateStore> {
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

View file

@ -22,6 +22,8 @@ import bisq.core.payment.PaymentAccount;
import bisq.core.trade.handlers.TransactionResultHandler;
import bisq.core.trade.statistics.TradeStatistics2;
import bisq.common.config.Config;
import bisq.proto.grpc.GetBalanceGrpc;
import bisq.proto.grpc.GetBalanceReply;
import bisq.proto.grpc.GetBalanceRequest;
@ -40,99 +42,112 @@ import bisq.proto.grpc.GetVersionRequest;
import bisq.proto.grpc.PlaceOfferGrpc;
import bisq.proto.grpc.PlaceOfferReply;
import bisq.proto.grpc.PlaceOfferRequest;
import bisq.proto.grpc.StopServerGrpc;
import bisq.proto.grpc.StopServerReply;
import bisq.proto.grpc.StopServerRequest;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
/**
* gRPC server. Gets a instance of BisqFacade passed to access data from the running Bisq instance.
*/
@Slf4j
public class BisqGrpcServer {
public class GrpcServer {
private Server server;
private final CoreApi coreApi;
private final int port;
private static BisqGrpcServer instance;
private static CoreApi coreApi;
public GrpcServer(Config config, CoreApi coreApi) {
this.coreApi = coreApi;
this.port = config.apiPort;
try {
var server = ServerBuilder.forPort(port)
.addService(new GetVersionService())
.addService(new GetBalanceService())
.addService(new GetTradeStatisticsService())
.addService(new GetOffersService())
.addService(new GetPaymentAccountsService())
.addService(new PlaceOfferService())
.intercept(new PasswordAuthInterceptor(config.apiPassword))
.build()
.start();
///////////////////////////////////////////////////////////////////////////////////////////
// Services
///////////////////////////////////////////////////////////////////////////////////////////
log.info("listening on port {}", port);
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
server.shutdown();
log.info("shutdown complete");
}));
static class GetVersionImpl extends GetVersionGrpc.GetVersionImplBase {
} catch (IOException e) {
log.error(e.toString(), e);
}
}
class GetVersionService extends GetVersionGrpc.GetVersionImplBase {
@Override
public void getVersion(GetVersionRequest req, StreamObserver<GetVersionReply> responseObserver) {
GetVersionReply reply = GetVersionReply.newBuilder().setVersion(coreApi.getVersion()).build();
var reply = GetVersionReply.newBuilder().setVersion(coreApi.getVersion()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
static class GetBalanceImpl extends GetBalanceGrpc.GetBalanceImplBase {
class GetBalanceService extends GetBalanceGrpc.GetBalanceImplBase {
@Override
public void getBalance(GetBalanceRequest req, StreamObserver<GetBalanceReply> responseObserver) {
GetBalanceReply reply = GetBalanceReply.newBuilder().setBalance(coreApi.getAvailableBalance()).build();
var reply = GetBalanceReply.newBuilder().setBalance(coreApi.getAvailableBalance()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
static class GetTradeStatisticsImpl extends GetTradeStatisticsGrpc.GetTradeStatisticsImplBase {
class GetTradeStatisticsService extends GetTradeStatisticsGrpc.GetTradeStatisticsImplBase {
@Override
public void getTradeStatistics(GetTradeStatisticsRequest req,
StreamObserver<GetTradeStatisticsReply> responseObserver) {
List<protobuf.TradeStatistics2> tradeStatistics = coreApi.getTradeStatistics().stream()
var tradeStatistics = coreApi.getTradeStatistics().stream()
.map(TradeStatistics2::toProtoTradeStatistics2)
.collect(Collectors.toList());
GetTradeStatisticsReply reply = GetTradeStatisticsReply.newBuilder().addAllTradeStatistics(tradeStatistics).build();
var reply = GetTradeStatisticsReply.newBuilder().addAllTradeStatistics(tradeStatistics).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
static class GetOffersImpl extends GetOffersGrpc.GetOffersImplBase {
class GetOffersService extends GetOffersGrpc.GetOffersImplBase {
@Override
public void getOffers(GetOffersRequest req, StreamObserver<GetOffersReply> responseObserver) {
List<protobuf.Offer> tradeStatistics = coreApi.getOffers().stream()
var tradeStatistics = coreApi.getOffers().stream()
.map(Offer::toProtoMessage)
.collect(Collectors.toList());
GetOffersReply reply = GetOffersReply.newBuilder().addAllOffers(tradeStatistics).build();
var reply = GetOffersReply.newBuilder().addAllOffers(tradeStatistics).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
static class GetPaymentAccountsImpl extends GetPaymentAccountsGrpc.GetPaymentAccountsImplBase {
class GetPaymentAccountsService extends GetPaymentAccountsGrpc.GetPaymentAccountsImplBase {
@Override
public void getPaymentAccounts(GetPaymentAccountsRequest req,
StreamObserver<GetPaymentAccountsReply> responseObserver) {
List<protobuf.PaymentAccount> tradeStatistics = coreApi.getPaymentAccounts().stream()
var tradeStatistics = coreApi.getPaymentAccounts().stream()
.map(PaymentAccount::toProtoMessage)
.collect(Collectors.toList());
GetPaymentAccountsReply reply = GetPaymentAccountsReply.newBuilder().addAllPaymentAccounts(tradeStatistics).build();
var reply = GetPaymentAccountsReply.newBuilder().addAllPaymentAccounts(tradeStatistics).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
static class PlaceOfferImpl extends PlaceOfferGrpc.PlaceOfferImplBase {
class PlaceOfferService extends PlaceOfferGrpc.PlaceOfferImplBase {
@Override
public void placeOffer(PlaceOfferRequest req, StreamObserver<PlaceOfferReply> responseObserver) {
TransactionResultHandler resultHandler = transaction -> {
@ -153,73 +168,4 @@ public class BisqGrpcServer {
resultHandler);
}
}
static class StopServerImpl extends StopServerGrpc.StopServerImplBase {
@Override
public void stopServer(StopServerRequest req, StreamObserver<StopServerReply> responseObserver) {
StopServerReply reply = StopServerReply.newBuilder().build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
instance.stop();
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public BisqGrpcServer(CoreApi coreApi) {
instance = this;
BisqGrpcServer.coreApi = coreApi;
try {
start();
} catch (IOException e) {
log.error(e.toString(), e);
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void stop() {
if (server != null) {
server.shutdown();
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void start() throws IOException {
// TODO add to options
int port = 9998;
// Config services
server = ServerBuilder.forPort(port)
.addService(new GetVersionImpl())
.addService(new GetBalanceImpl())
.addService(new GetTradeStatisticsImpl())
.addService(new GetOffersImpl())
.addService(new GetPaymentAccountsImpl())
.addService(new PlaceOfferImpl())
.addService(new StopServerImpl())
.build()
.start();
log.info("Server started, listening on " + port);
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
// Use stderr here since the logger may have been reset by its JVM shutdown hook.
log.error("*** shutting down gRPC server since JVM is shutting down");
BisqGrpcServer.this.stop();
log.error("*** server shut down");
}));
}
}

View file

@ -0,0 +1,45 @@
package bisq.core.grpc;
import io.grpc.Metadata;
import io.grpc.ServerCall;
import io.grpc.ServerCallHandler;
import io.grpc.ServerInterceptor;
import io.grpc.StatusRuntimeException;
import static io.grpc.Metadata.ASCII_STRING_MARSHALLER;
import static io.grpc.Metadata.Key;
import static io.grpc.Status.UNAUTHENTICATED;
import static java.lang.String.format;
/**
* Authorizes rpc server calls by comparing the value of the caller's
* {@value PASSWORD_KEY} header to an expected value set at server startup time.
*
* @see bisq.common.config.Config#apiPassword
*/
class PasswordAuthInterceptor implements ServerInterceptor {
public static final String PASSWORD_KEY = "password";
private final String expectedPasswordValue;
public PasswordAuthInterceptor(String expectedPasswordValue) {
this.expectedPasswordValue = expectedPasswordValue;
}
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall, Metadata headers,
ServerCallHandler<ReqT, RespT> serverCallHandler) {
var actualPasswordValue = headers.get(Key.of(PASSWORD_KEY, ASCII_STRING_MARSHALLER));
if (actualPasswordValue == null)
throw new StatusRuntimeException(UNAUTHENTICATED.withDescription(
format("missing '%s' rpc header value", PASSWORD_KEY)));
if (!actualPasswordValue.equals(expectedPasswordValue))
throw new StatusRuntimeException(UNAUTHENTICATED.withDescription(
format("incorrect '%s' rpc header value", PASSWORD_KEY)));
return serverCallHandler.startCall(serverCall, headers);
}
}

View file

@ -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

View file

@ -20,7 +20,7 @@ package bisq.daemon.app;
import bisq.core.app.BisqHeadlessAppMain;
import bisq.core.app.BisqSetup;
import bisq.core.app.CoreModule;
import bisq.core.grpc.BisqGrpcServer;
import bisq.core.grpc.GrpcServer;
import bisq.core.grpc.CoreApi;
import bisq.common.UserThread;
@ -99,6 +99,6 @@ public class BisqDaemonMain extends BisqHeadlessAppMain implements BisqSetup.Bis
super.onApplicationStarted();
CoreApi coreApi = injector.getInstance(CoreApi.class);
new BisqGrpcServer(coreApi);
new GrpcServer(config, coreApi);
}
}

View file

@ -18,7 +18,6 @@
package bisq.desktop.main.dao.monitor;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.AutoTooltipButton;
import bisq.desktop.components.AutoTooltipLabel;
import bisq.desktop.components.AutoTooltipTableColumn;
@ -40,8 +39,6 @@ import bisq.core.locale.Res;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.seed.SeedNodeRepository;
import bisq.common.storage.FileManager;
import de.jensd.fx.fontawesome.AwesomeIcon;
import javafx.scene.control.Button;
@ -140,33 +137,7 @@ public abstract class StateMonitorView<StH extends StateHash,
resyncButton.visibleProperty().bind(isInConflictWithSeedNode);
resyncButton.managedProperty().bind(isInConflictWithSeedNode);
resyncButton.setOnAction(ev -> {
try {
// We delete all consensus payload data and reset the daoState so it will rebuild from genesis.
// Deleting the daoState would cause to read the file from the resources and we would not rebuild from
// genesis if a snapshot exist!
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);
daoFacade.resyncDao(() -> new Popup().attention(Res.get("setting.preferences.dao.resync.popup"))
.useShutDownButton()
.hideCloseButton()
.show());
} catch (Throwable t) {
t.printStackTrace();
log.error(t.toString());
new Popup().error(t.toString()).show();
}
});
resyncButton.setOnAction(ev -> resyncDaoState());
if (daoStateService.isParseBlockChainComplete()) {
onDataUpdate();
@ -294,7 +265,9 @@ public abstract class StateMonitorView<StH extends StateHash,
protected void onDataUpdate() {
if (isInConflictWithSeedNode.get()) {
statusTextField.setText(Res.get("dao.monitor.isInConflictWithSeedNode"));
String msg = Res.get("dao.monitor.isInConflictWithSeedNode");
log.warn(msg);
statusTextField.setText(msg);
statusTextField.getStyleClass().add("dao-inConflict");
} else if (isInConflictWithNonSeedNode.get()) {
statusTextField.setText(Res.get("dao.monitor.isInConflictWithNonSeedNode"));
@ -307,6 +280,20 @@ public abstract class StateMonitorView<StH extends StateHash,
GUIUtil.setFitToRowsForTableView(tableView, 25, 28, 2, 5);
}
private void resyncDaoState() {
try {
daoFacade.resyncDaoStateFromResources(storageDir);
new Popup().attention(Res.get("setting.preferences.dao.resyncFromResources.popup"))
.useShutDownButton()
.hideCloseButton()
.show();
} catch (Throwable t) {
t.printStackTrace();
log.error(t.toString());
new Popup().error(t.toString()).show();
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// TableColumns

View file

@ -17,6 +17,7 @@
package bisq.desktop.main.settings.preferences;
import bisq.desktop.app.BisqApp;
import bisq.desktop.common.view.ActivatableViewAndModel;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.AutoTooltipButton;
@ -88,6 +89,8 @@ import javafx.collections.ObservableList;
import javafx.util.Callback;
import javafx.util.StringConverter;
import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
@ -123,12 +126,13 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
private final AssetService assetService;
private final FilterManager filterManager;
private final DaoFacade daoFacade;
private final File storageDir;
private ListView<FiatCurrency> fiatCurrenciesListView;
private ComboBox<FiatCurrency> fiatCurrenciesComboBox;
private ListView<CryptoCurrency> cryptoCurrenciesListView;
private ComboBox<CryptoCurrency> cryptoCurrenciesComboBox;
private Button resetDontShowAgainButton, resyncDaoButton;
private Button resetDontShowAgainButton, resyncDaoFromGenesisButton, resyncDaoFromResourcesButton;
// private ListChangeListener<TradeCurrency> displayCurrenciesListChangeListener;
private ObservableList<BlockChainExplorer> blockExplorers;
private ObservableList<BlockChainExplorer> bsqBlockChainExplorers;
@ -162,13 +166,15 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
Config config,
@Named(Config.RPC_USER) String rpcUser,
@Named(Config.RPC_PASSWORD) String rpcPassword,
@Named(Config.RPC_BLOCK_NOTIFICATION_PORT) int rpcBlockNotificationPort) {
@Named(Config.RPC_BLOCK_NOTIFICATION_PORT) int rpcBlockNotificationPort,
@Named(Config.STORAGE_DIR) File storageDir) {
super(model);
this.preferences = preferences;
this.feeService = feeService;
this.assetService = assetService;
this.filterManager = filterManager;
this.daoFacade = daoFacade;
this.storageDir = storageDir;
daoOptionsSet = config.fullDaoNodeOptionSetExplicitly &&
!rpcUser.isEmpty() &&
!rpcPassword.isEmpty() &&
@ -600,10 +606,14 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
}
private void initializeDaoOptions() {
daoOptionsTitledGroupBg = addTitledGroupBg(root, ++gridRow, 1, Res.get("setting.preferences.daoOptions"), Layout.GROUP_DISTANCE);
resyncDaoButton = addButton(root, gridRow, Res.get("setting.preferences.dao.resync.label"), Layout.TWICE_FIRST_ROW_AND_GROUP_DISTANCE);
resyncDaoButton.setMaxWidth(Double.MAX_VALUE);
GridPane.setHgrow(resyncDaoButton, Priority.ALWAYS);
daoOptionsTitledGroupBg = addTitledGroupBg(root, ++gridRow, 2, Res.get("setting.preferences.daoOptions"), Layout.GROUP_DISTANCE);
resyncDaoFromResourcesButton = addButton(root, gridRow, Res.get("setting.preferences.dao.resyncFromResources.label"), Layout.TWICE_FIRST_ROW_AND_GROUP_DISTANCE);
resyncDaoFromResourcesButton.setMaxWidth(Double.MAX_VALUE);
GridPane.setHgrow(resyncDaoFromResourcesButton, Priority.ALWAYS);
resyncDaoFromGenesisButton = addButton(root, ++gridRow, Res.get("setting.preferences.dao.resyncFromGenesis.label"));
resyncDaoFromGenesisButton.setMaxWidth(Double.MAX_VALUE);
GridPane.setHgrow(resyncDaoFromGenesisButton, Priority.ALWAYS);
isDaoFullNodeToggleButton = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.dao.isDaoFullNode"));
rpcUserTextField = addInputTextField(root, ++gridRow, Res.get("setting.preferences.dao.rpcUser"));
@ -865,11 +875,26 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
blockNotifyPortTextField.setText(blockNotifyPort > 0 ? String.valueOf(blockNotifyPort) : "");
updateDaoFields();
resyncDaoButton.setOnAction(e -> daoFacade.resyncDao(() ->
new Popup().attention(Res.get("setting.preferences.dao.resync.popup"))
resyncDaoFromResourcesButton.setOnAction(e -> {
try {
daoFacade.resyncDaoStateFromResources(storageDir);
new Popup().attention(Res.get("setting.preferences.dao.resyncFromResources.popup"))
.useShutDownButton()
.hideCloseButton()
.show()));
.show();
} catch (Throwable t) {
t.printStackTrace();
log.error(t.toString());
new Popup().error(t.toString()).show();
}
});
resyncDaoFromGenesisButton.setOnAction(e ->
new Popup().attention(Res.get("setting.preferences.dao.resyncFromGenesis.popup"))
.actionButtonText(Res.get("setting.preferences.dao.resyncFromGenesis.resync"))
.onAction(() -> daoFacade.resyncDaoStateFromGenesis(() -> BisqApp.getShutDownHandler().run()))
.closeButtonText(Res.get("shared.cancel"))
.show());
isDaoFullNodeToggleButton.setOnAction(e -> {
String key = "daoFullModeInfoShown";
@ -973,7 +998,8 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
}
private void deactivateDaoPreferences() {
resyncDaoButton.setOnAction(null);
resyncDaoFromResourcesButton.setOnAction(null);
resyncDaoFromGenesisButton.setOnAction(null);
isDaoFullNodeToggleButton.setOnAction(null);
rpcUserTextField.textProperty().removeListener(rpcUserListener);
rpcPwTextField.textProperty().removeListener(rpcPwListener);

View file

@ -1,6 +1,14 @@
# Building Bisq
## Install Git LFS
Bisq uses Git LFS to track certain large binary files. Follow the instructions at https://git-lfs.github.com to install it, then run the following to command to verify the installation:
$ git lfs version
git-lfs/2.10.0 (GitHub; darwin amd64; go 1.13.6)
## Clone
git clone https://github.com/bisq-network/bisq
@ -20,7 +28,7 @@ If on Windows run `gradlew.bat build` instead.
Bisq executables are now available in the root project directory. Run Bisq Desktop as follows:
Note: bisq runs fine on jdk10 and jdk11. jdk12 is currently not supported.
Note: bisq runs fine on jdk10 and jdk11. jdk12 is currently not supported.
./bisq-desktop

View file

@ -19,6 +19,7 @@ package bisq.network.p2p.network;
import bisq.network.p2p.NodeAddress;
import bisq.common.Timer;
import bisq.common.UserThread;
import bisq.common.proto.network.NetworkEnvelope;
import bisq.common.proto.network.NetworkProtoResolver;
@ -36,9 +37,6 @@ import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import java.time.Duration;
import java.time.LocalTime;
import java.net.ConnectException;
import java.net.ServerSocket;
import java.net.Socket;
@ -51,6 +49,7 @@ import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.slf4j.Logger;
@ -333,27 +332,29 @@ public abstract class NetworkNode implements MessageListener {
server = null;
}
getAllConnections().parallelStream().forEach(c -> c.shutDown(CloseConnectionReason.APP_SHUT_DOWN));
// wait for connections to actually close, c.shutDown may create threads to actually close connections...
LocalTime timeout = LocalTime.now().plus(Duration.ofSeconds(15));
while (!getAllConnections().isEmpty()) {
// check timeout
if (timeout.isBefore(LocalTime.now())) {
log.error("Could not close all connections within timeout (" + getAllConnections().size() + " connections remaining). Moving on.");
break;
Set<Connection> allConnections = getAllConnections();
int numConnections = allConnections.size();
log.info("Shutdown {} connections", numConnections);
AtomicInteger shutdownCompleted = new AtomicInteger();
Timer timeoutHandler = UserThread.runAfter(() -> {
if (shutDownCompleteHandler != null) {
log.info("Shutdown completed due timeout");
shutDownCompleteHandler.run();
}
try {
// reduce system load
Thread.sleep(10);
} catch (InterruptedException e) {
// ignore
}
}
log.debug("NetworkNode shutdown complete");
}, 3);
allConnections.forEach(c -> c.shutDown(CloseConnectionReason.APP_SHUT_DOWN,
() -> {
shutdownCompleted.getAndIncrement();
log.info("Shutdown o fnode {} completed", c.getPeersNodeAddressOptional());
if (shutdownCompleted.get() == numConnections) {
log.info("Shutdown completed with all connections closed");
timeoutHandler.stop();
if (shutDownCompleteHandler != null) {
shutDownCompleteHandler.run();
}
}
}));
}
if (shutDownCompleteHandler != null) shutDownCompleteHandler.run();
}

File diff suppressed because one or more lines are too long

View file

@ -127,18 +127,3 @@ message PlaceOfferRequest {
message PlaceOfferReply {
bool result = 1;
}
///////////////////////////////////////////////////////////////////////////////////////////
// StopServer
///////////////////////////////////////////////////////////////////////////////////////////
service StopServer {
rpc StopServer (StopServerRequest) returns (StopServerReply) {
}
}
message StopServerRequest {
}
message StopServerReply {
}