mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-22 14:42:37 +01:00
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:
commit
d9fe059e42
32 changed files with 796 additions and 320000 deletions
3
.gitattributes
vendored
3
.gitattributes
vendored
|
@ -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
|
||||
|
|
10
.travis.yml
10
.travis.yml
|
@ -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
|
||||
|
|
13
build.gradle
13
build.gradle
|
@ -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')
|
||||
|
|
189
cli/src/main/java/bisq/cli/CliMain.java
Normal file
189
cli/src/main/java/bisq/cli/CliMain.java
Normal 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));
|
||||
}
|
||||
}
|
45
cli/src/main/java/bisq/cli/PasswordCallCredentials.java
Normal file
45
cli/src/main/java/bisq/cli/PasswordCallCredentials.java
Normal 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() {
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
156
cli/test.sh
Executable 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
|
|
@ -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),
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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<>();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
}));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
|
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -127,18 +127,3 @@ message PlaceOfferRequest {
|
|||
message PlaceOfferReply {
|
||||
bool result = 1;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// StopServer
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
service StopServer {
|
||||
rpc StopServer (StopServerRequest) returns (StopServerReply) {
|
||||
}
|
||||
}
|
||||
|
||||
message StopServerRequest {
|
||||
}
|
||||
|
||||
message StopServerReply {
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue