mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 01:41:11 +01:00
Merge master
- fix tests
This commit is contained in:
commit
039860935d
@ -1,11 +1,11 @@
|
|||||||
#!/usr/bin/env bats
|
#!/usr/bin/env bats
|
||||||
#
|
#
|
||||||
# Integration tests for bisq-cli running against a live bisq-daemon
|
# Smoke tests for bisq-cli running against a live bisq-daemon (on mainnet)
|
||||||
#
|
#
|
||||||
# Prerequisites:
|
# Prerequisites:
|
||||||
#
|
#
|
||||||
# - bats v0.4.0 must be installed (brew install bats on macOS)
|
# - bats-core 1.2.0+ must be installed (brew install bats-core on macOS)
|
||||||
# see https://github.com/sstephenson/bats/tree/v0.4.0
|
# see https://github.com/bats-core/bats-core
|
||||||
#
|
#
|
||||||
# - Run `./bisq-daemon --apiPassword=xyz --appDataDir=$TESTDIR` where $TESTDIR
|
# - Run `./bisq-daemon --apiPassword=xyz --appDataDir=$TESTDIR` where $TESTDIR
|
||||||
# is empty or otherwise contains an unencrypted wallet with a 0 BTC balance
|
# is empty or otherwise contains an unencrypted wallet with a 0 BTC balance
|
||||||
@ -48,14 +48,14 @@
|
|||||||
run ./bisq-cli --password="xyz" getversion
|
run ./bisq-cli --password="xyz" getversion
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
echo "actual output: $output" >&2
|
echo "actual output: $output" >&2
|
||||||
[ "$output" = "1.3.5" ]
|
[ "$output" = "1.3.7" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "test getversion" {
|
@test "test getversion" {
|
||||||
run ./bisq-cli --password=xyz getversion
|
run ./bisq-cli --password=xyz getversion
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
echo "actual output: $output" >&2
|
echo "actual output: $output" >&2
|
||||||
[ "$output" = "1.3.5" ]
|
[ "$output" = "1.3.7" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "test setwalletpassword \"a b c\"" {
|
@test "test setwalletpassword \"a b c\"" {
|
||||||
@ -190,8 +190,8 @@
|
|||||||
[ "$output" = "Error: incorrect parameter count, expecting direction (buy|sell), currency code" ]
|
[ "$output" = "Error: incorrect parameter count, expecting direction (buy|sell), currency code" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "test getoffers buy eur check return status" {
|
@test "test getoffers sell eur check return status" {
|
||||||
run ./bisq-cli --password=xyz getoffers buy eur
|
run ./bisq-cli --password=xyz getoffers sell eur
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
}
|
}
|
||||||
|
|
@ -311,17 +311,25 @@ public class Scaffold {
|
|||||||
bitcoinDaemon.verifyBitcoindRunning();
|
bitcoinDaemon.verifyBitcoindRunning();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start Bisq apps defined by the supportingApps option, in the in proper order.
|
||||||
|
|
||||||
if (config.hasSupportingApp(seednode.name()))
|
if (config.hasSupportingApp(seednode.name()))
|
||||||
startBisqApp(seednode, executor, countdownLatch);
|
startBisqApp(seednode, executor, countdownLatch);
|
||||||
|
|
||||||
if (config.hasSupportingApp(arbdaemon.name(), arbdesktop.name()))
|
if (config.hasSupportingApp(arbdaemon.name()))
|
||||||
startBisqApp(config.runArbNodeAsDesktop ? arbdesktop : arbdaemon, executor, countdownLatch);
|
startBisqApp(arbdaemon, executor, countdownLatch);
|
||||||
|
else if (config.hasSupportingApp(arbdesktop.name()))
|
||||||
|
startBisqApp(arbdesktop, executor, countdownLatch);
|
||||||
|
|
||||||
if (config.hasSupportingApp(alicedaemon.name(), alicedesktop.name()))
|
if (config.hasSupportingApp(alicedaemon.name()))
|
||||||
startBisqApp(config.runAliceNodeAsDesktop ? alicedesktop : alicedaemon, executor, countdownLatch);
|
startBisqApp(alicedaemon, executor, countdownLatch);
|
||||||
|
else if (config.hasSupportingApp(alicedesktop.name()))
|
||||||
|
startBisqApp(alicedesktop, executor, countdownLatch);
|
||||||
|
|
||||||
if (config.hasSupportingApp(bobdaemon.name(), bobdesktop.name()))
|
if (config.hasSupportingApp(bobdaemon.name()))
|
||||||
startBisqApp(config.runBobNodeAsDesktop ? bobdesktop : bobdaemon, executor, countdownLatch);
|
startBisqApp(bobdaemon, executor, countdownLatch);
|
||||||
|
else if (config.hasSupportingApp(bobdesktop.name()))
|
||||||
|
startBisqApp(bobdesktop, executor, countdownLatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startBisqApp(BisqAppConfig bisqAppConfig,
|
private void startBisqApp(BisqAppConfig bisqAppConfig,
|
||||||
@ -329,28 +337,24 @@ public class Scaffold {
|
|||||||
CountDownLatch countdownLatch)
|
CountDownLatch countdownLatch)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
|
|
||||||
BisqApp bisqApp;
|
BisqApp bisqApp = createBisqApp(bisqAppConfig);
|
||||||
switch (bisqAppConfig) {
|
switch (bisqAppConfig) {
|
||||||
case seednode:
|
case seednode:
|
||||||
bisqApp = createBisqApp(seednode);
|
|
||||||
seedNodeTask = new SetupTask(bisqApp, countdownLatch);
|
seedNodeTask = new SetupTask(bisqApp, countdownLatch);
|
||||||
seedNodeTaskFuture = executor.submit(seedNodeTask);
|
seedNodeTaskFuture = executor.submit(seedNodeTask);
|
||||||
break;
|
break;
|
||||||
case arbdaemon:
|
case arbdaemon:
|
||||||
case arbdesktop:
|
case arbdesktop:
|
||||||
bisqApp = createBisqApp(config.runArbNodeAsDesktop ? arbdesktop : arbdaemon);
|
|
||||||
arbNodeTask = new SetupTask(bisqApp, countdownLatch);
|
arbNodeTask = new SetupTask(bisqApp, countdownLatch);
|
||||||
arbNodeTaskFuture = executor.submit(arbNodeTask);
|
arbNodeTaskFuture = executor.submit(arbNodeTask);
|
||||||
break;
|
break;
|
||||||
case alicedaemon:
|
case alicedaemon:
|
||||||
case alicedesktop:
|
case alicedesktop:
|
||||||
bisqApp = createBisqApp(config.runAliceNodeAsDesktop ? alicedesktop : alicedaemon);
|
|
||||||
aliceNodeTask = new SetupTask(bisqApp, countdownLatch);
|
aliceNodeTask = new SetupTask(bisqApp, countdownLatch);
|
||||||
aliceNodeTaskFuture = executor.submit(aliceNodeTask);
|
aliceNodeTaskFuture = executor.submit(aliceNodeTask);
|
||||||
break;
|
break;
|
||||||
case bobdaemon:
|
case bobdaemon:
|
||||||
case bobdesktop:
|
case bobdesktop:
|
||||||
bisqApp = createBisqApp(config.runBobNodeAsDesktop ? bobdesktop : bobdaemon);
|
|
||||||
bobNodeTask = new SetupTask(bisqApp, countdownLatch);
|
bobNodeTask = new SetupTask(bisqApp, countdownLatch);
|
||||||
bobNodeTaskFuture = executor.submit(bobNodeTask);
|
bobNodeTaskFuture = executor.submit(bobNodeTask);
|
||||||
break;
|
break;
|
||||||
|
@ -65,13 +65,11 @@ public class ApiTestConfig {
|
|||||||
static final String ROOT_APP_DATA_DIR = "rootAppDataDir";
|
static final String ROOT_APP_DATA_DIR = "rootAppDataDir";
|
||||||
static final String API_PASSWORD = "apiPassword";
|
static final String API_PASSWORD = "apiPassword";
|
||||||
static final String RUN_SUBPROJECT_JARS = "runSubprojectJars";
|
static final String RUN_SUBPROJECT_JARS = "runSubprojectJars";
|
||||||
static final String RUN_ARB_NODE_AS_DESKTOP = "runArbNodeAsDesktop";
|
|
||||||
static final String RUN_ALICE_NODE_AS_DESKTOP = "runAliceNodeAsDesktop";
|
|
||||||
static final String RUN_BOB_NODE_AS_DESKTOP = "runBobNodeAsDesktop";
|
|
||||||
static final String BISQ_APP_INIT_TIME = "bisqAppInitTime";
|
static final String BISQ_APP_INIT_TIME = "bisqAppInitTime";
|
||||||
static final String SKIP_TESTS = "skipTests";
|
static final String SKIP_TESTS = "skipTests";
|
||||||
static final String SHUTDOWN_AFTER_TESTS = "shutdownAfterTests";
|
static final String SHUTDOWN_AFTER_TESTS = "shutdownAfterTests";
|
||||||
static final String SUPPORTING_APPS = "supportingApps";
|
static final String SUPPORTING_APPS = "supportingApps";
|
||||||
|
static final String ENABLE_BISQ_DEBUGGING = "enableBisqDebugging";
|
||||||
|
|
||||||
// Default values for certain options
|
// Default values for certain options
|
||||||
static final String DEFAULT_CONFIG_FILE_NAME = "apitest.properties";
|
static final String DEFAULT_CONFIG_FILE_NAME = "apitest.properties";
|
||||||
@ -98,13 +96,11 @@ public class ApiTestConfig {
|
|||||||
// Daemon instances can use same gRPC password, but each needs a different apiPort.
|
// Daemon instances can use same gRPC password, but each needs a different apiPort.
|
||||||
public final String apiPassword;
|
public final String apiPassword;
|
||||||
public final boolean runSubprojectJars;
|
public final boolean runSubprojectJars;
|
||||||
public final boolean runArbNodeAsDesktop;
|
|
||||||
public final boolean runAliceNodeAsDesktop;
|
|
||||||
public final boolean runBobNodeAsDesktop;
|
|
||||||
public final long bisqAppInitTime;
|
public final long bisqAppInitTime;
|
||||||
public final boolean skipTests;
|
public final boolean skipTests;
|
||||||
public final boolean shutdownAfterTests;
|
public final boolean shutdownAfterTests;
|
||||||
public final List<String> supportingApps;
|
public final List<String> supportingApps;
|
||||||
|
public final boolean enableBisqDebugging;
|
||||||
|
|
||||||
// Immutable system configurations set in the constructor.
|
// Immutable system configurations set in the constructor.
|
||||||
public final String bitcoinDatadir;
|
public final String bitcoinDatadir;
|
||||||
@ -202,27 +198,6 @@ public class ApiTestConfig {
|
|||||||
.ofType(Boolean.class)
|
.ofType(Boolean.class)
|
||||||
.defaultsTo(false);
|
.defaultsTo(false);
|
||||||
|
|
||||||
ArgumentAcceptingOptionSpec<Boolean> runArbNodeAsDesktopOpt =
|
|
||||||
parser.accepts(RUN_ARB_NODE_AS_DESKTOP,
|
|
||||||
"Run Arbitration node as desktop")
|
|
||||||
.withRequiredArg()
|
|
||||||
.ofType(Boolean.class)
|
|
||||||
.defaultsTo(false); // TODO how do I register mediator?
|
|
||||||
|
|
||||||
ArgumentAcceptingOptionSpec<Boolean> runAliceNodeAsDesktopOpt =
|
|
||||||
parser.accepts(RUN_ALICE_NODE_AS_DESKTOP,
|
|
||||||
"Run Alice node as desktop")
|
|
||||||
.withRequiredArg()
|
|
||||||
.ofType(Boolean.class)
|
|
||||||
.defaultsTo(false);
|
|
||||||
|
|
||||||
ArgumentAcceptingOptionSpec<Boolean> runBobNodeAsDesktopOpt =
|
|
||||||
parser.accepts(RUN_BOB_NODE_AS_DESKTOP,
|
|
||||||
"Run Bob node as desktop")
|
|
||||||
.withRequiredArg()
|
|
||||||
.ofType(Boolean.class)
|
|
||||||
.defaultsTo(false);
|
|
||||||
|
|
||||||
ArgumentAcceptingOptionSpec<Long> bisqAppInitTimeOpt =
|
ArgumentAcceptingOptionSpec<Long> bisqAppInitTimeOpt =
|
||||||
parser.accepts(BISQ_APP_INIT_TIME,
|
parser.accepts(BISQ_APP_INIT_TIME,
|
||||||
"Amount of time (ms) to wait on a Bisq instance's initialization")
|
"Amount of time (ms) to wait on a Bisq instance's initialization")
|
||||||
@ -251,6 +226,12 @@ public class ApiTestConfig {
|
|||||||
.ofType(String.class)
|
.ofType(String.class)
|
||||||
.defaultsTo("bitcoind,seednode,arbdaemon,alicedaemon,bobdaemon");
|
.defaultsTo("bitcoind,seednode,arbdaemon,alicedaemon,bobdaemon");
|
||||||
|
|
||||||
|
ArgumentAcceptingOptionSpec<Boolean> enableBisqDebuggingOpt =
|
||||||
|
parser.accepts(ENABLE_BISQ_DEBUGGING,
|
||||||
|
"Start Bisq apps with remote debug options")
|
||||||
|
.withRequiredArg()
|
||||||
|
.ofType(Boolean.class)
|
||||||
|
.defaultsTo(false);
|
||||||
try {
|
try {
|
||||||
CompositeOptionSet options = new CompositeOptionSet();
|
CompositeOptionSet options = new CompositeOptionSet();
|
||||||
|
|
||||||
@ -302,13 +283,11 @@ public class ApiTestConfig {
|
|||||||
this.bitcoinRpcPassword = options.valueOf(bitcoinRpcPasswordOpt);
|
this.bitcoinRpcPassword = options.valueOf(bitcoinRpcPasswordOpt);
|
||||||
this.apiPassword = options.valueOf(apiPasswordOpt);
|
this.apiPassword = options.valueOf(apiPasswordOpt);
|
||||||
this.runSubprojectJars = options.valueOf(runSubprojectJarsOpt);
|
this.runSubprojectJars = options.valueOf(runSubprojectJarsOpt);
|
||||||
this.runArbNodeAsDesktop = options.valueOf(runArbNodeAsDesktopOpt);
|
|
||||||
this.runAliceNodeAsDesktop = options.valueOf(runAliceNodeAsDesktopOpt);
|
|
||||||
this.runBobNodeAsDesktop = options.valueOf(runBobNodeAsDesktopOpt);
|
|
||||||
this.bisqAppInitTime = options.valueOf(bisqAppInitTimeOpt);
|
this.bisqAppInitTime = options.valueOf(bisqAppInitTimeOpt);
|
||||||
this.skipTests = options.valueOf(skipTestsOpt);
|
this.skipTests = options.valueOf(skipTestsOpt);
|
||||||
this.shutdownAfterTests = options.valueOf(shutdownAfterTestsOpt);
|
this.shutdownAfterTests = options.valueOf(shutdownAfterTestsOpt);
|
||||||
this.supportingApps = asList(options.valueOf(supportingAppsOpt).split(","));
|
this.supportingApps = asList(options.valueOf(supportingAppsOpt).split(","));
|
||||||
|
this.enableBisqDebugging = options.valueOf(enableBisqDebuggingOpt);
|
||||||
|
|
||||||
// Assign values to special-case static fields.
|
// Assign values to special-case static fields.
|
||||||
BASH_PATH_VALUE = bashPath;
|
BASH_PATH_VALUE = bashPath;
|
||||||
|
@ -30,58 +30,64 @@ import bisq.daemon.app.BisqDaemonMain;
|
|||||||
@see <a href="https://github.com/bisq-network/bisq/blob/master/docs/dev-setup.md">dev-setup.md</a>
|
@see <a href="https://github.com/bisq-network/bisq/blob/master/docs/dev-setup.md">dev-setup.md</a>
|
||||||
@see <a href="https://github.com/bisq-network/bisq/blob/master/docs/dao-setup.md">dao-setup.md</a>
|
@see <a href="https://github.com/bisq-network/bisq/blob/master/docs/dao-setup.md">dao-setup.md</a>
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public enum BisqAppConfig {
|
public enum BisqAppConfig {
|
||||||
|
|
||||||
seednode("bisq-BTC_REGTEST_Seed_2002",
|
seednode("bisq-BTC_REGTEST_Seed_2002",
|
||||||
"bisq-seednode",
|
"bisq-seednode",
|
||||||
"\"-XX:MaxRAM=2g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml\"",
|
"-XX:MaxRAM=2g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml",
|
||||||
SeedNodeMain.class.getName(),
|
SeedNodeMain.class.getName(),
|
||||||
2002,
|
2002,
|
||||||
5120,
|
5120,
|
||||||
-1),
|
-1,
|
||||||
|
49996),
|
||||||
arbdaemon("bisq-BTC_REGTEST_Arb_dao",
|
arbdaemon("bisq-BTC_REGTEST_Arb_dao",
|
||||||
"bisq-daemon",
|
"bisq-daemon",
|
||||||
"\"-XX:MaxRAM=2g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml\"",
|
"-XX:MaxRAM=2g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml",
|
||||||
BisqDaemonMain.class.getName(),
|
BisqDaemonMain.class.getName(),
|
||||||
4444,
|
4444,
|
||||||
5121,
|
5121,
|
||||||
9997),
|
9997,
|
||||||
|
49997),
|
||||||
arbdesktop("bisq-BTC_REGTEST_Arb_dao",
|
arbdesktop("bisq-BTC_REGTEST_Arb_dao",
|
||||||
"bisq-desktop",
|
"bisq-desktop",
|
||||||
"\"-XX:MaxRAM=3g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml\"",
|
"-XX:MaxRAM=3g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml",
|
||||||
BisqAppMain.class.getName(),
|
BisqAppMain.class.getName(),
|
||||||
4444,
|
4444,
|
||||||
5121,
|
5121,
|
||||||
-1),
|
-1,
|
||||||
|
49997),
|
||||||
alicedaemon("bisq-BTC_REGTEST_Alice_dao",
|
alicedaemon("bisq-BTC_REGTEST_Alice_dao",
|
||||||
"bisq-daemon",
|
"bisq-daemon",
|
||||||
"\"-XX:MaxRAM=2g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml\"",
|
"-XX:MaxRAM=2g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml",
|
||||||
BisqDaemonMain.class.getName(),
|
BisqDaemonMain.class.getName(),
|
||||||
7777,
|
7777,
|
||||||
5122,
|
5122,
|
||||||
9998),
|
9998,
|
||||||
|
49998),
|
||||||
alicedesktop("bisq-BTC_REGTEST_Alice_dao",
|
alicedesktop("bisq-BTC_REGTEST_Alice_dao",
|
||||||
"bisq-desktop",
|
"bisq-desktop",
|
||||||
"\"-XX:MaxRAM=4g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml\"",
|
"-XX:MaxRAM=4g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml",
|
||||||
BisqAppMain.class.getName(),
|
BisqAppMain.class.getName(),
|
||||||
7777,
|
7777,
|
||||||
5122,
|
5122,
|
||||||
-1),
|
-1,
|
||||||
|
49998),
|
||||||
bobdaemon("bisq-BTC_REGTEST_Bob_dao",
|
bobdaemon("bisq-BTC_REGTEST_Bob_dao",
|
||||||
"bisq-daemon",
|
"bisq-daemon",
|
||||||
"\"-XX:MaxRAM=2g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml\"",
|
"-XX:MaxRAM=2g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml",
|
||||||
BisqDaemonMain.class.getName(),
|
BisqDaemonMain.class.getName(),
|
||||||
8888,
|
8888,
|
||||||
5123,
|
5123,
|
||||||
9999),
|
9999,
|
||||||
|
49999),
|
||||||
bobdesktop("bisq-BTC_REGTEST_Bob_dao",
|
bobdesktop("bisq-BTC_REGTEST_Bob_dao",
|
||||||
"bisq-desktop",
|
"bisq-desktop",
|
||||||
"\"-XX:MaxRAM=4g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml\"",
|
"-XX:MaxRAM=4g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml",
|
||||||
BisqAppMain.class.getName(),
|
BisqAppMain.class.getName(),
|
||||||
8888,
|
8888,
|
||||||
5123,
|
5123,
|
||||||
-1);
|
-1,
|
||||||
|
49999);
|
||||||
|
|
||||||
public final String appName;
|
public final String appName;
|
||||||
public final String startupScript;
|
public final String startupScript;
|
||||||
@ -91,6 +97,7 @@ public enum BisqAppConfig {
|
|||||||
public final int rpcBlockNotificationPort;
|
public final int rpcBlockNotificationPort;
|
||||||
// Daemons can use a global gRPC password, but each needs a unique apiPort.
|
// Daemons can use a global gRPC password, but each needs a unique apiPort.
|
||||||
public final int apiPort;
|
public final int apiPort;
|
||||||
|
public final int remoteDebugPort;
|
||||||
|
|
||||||
BisqAppConfig(String appName,
|
BisqAppConfig(String appName,
|
||||||
String startupScript,
|
String startupScript,
|
||||||
@ -98,7 +105,8 @@ public enum BisqAppConfig {
|
|||||||
String mainClassName,
|
String mainClassName,
|
||||||
int nodePort,
|
int nodePort,
|
||||||
int rpcBlockNotificationPort,
|
int rpcBlockNotificationPort,
|
||||||
int apiPort) {
|
int apiPort,
|
||||||
|
int remoteDebugPort) {
|
||||||
this.appName = appName;
|
this.appName = appName;
|
||||||
this.startupScript = startupScript;
|
this.startupScript = startupScript;
|
||||||
this.javaOpts = javaOpts;
|
this.javaOpts = javaOpts;
|
||||||
@ -106,6 +114,7 @@ public enum BisqAppConfig {
|
|||||||
this.nodePort = nodePort;
|
this.nodePort = nodePort;
|
||||||
this.rpcBlockNotificationPort = rpcBlockNotificationPort;
|
this.rpcBlockNotificationPort = rpcBlockNotificationPort;
|
||||||
this.apiPort = apiPort;
|
this.apiPort = apiPort;
|
||||||
|
this.remoteDebugPort = remoteDebugPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -118,6 +127,7 @@ public enum BisqAppConfig {
|
|||||||
", nodePort=" + nodePort + "\n" +
|
", nodePort=" + nodePort + "\n" +
|
||||||
", rpcBlockNotificationPort=" + rpcBlockNotificationPort + "\n" +
|
", rpcBlockNotificationPort=" + rpcBlockNotificationPort + "\n" +
|
||||||
", apiPort=" + apiPort + "\n" +
|
", apiPort=" + apiPort + "\n" +
|
||||||
|
", remoteDebugPort=" + remoteDebugPort + "\n" +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,7 @@ public class BisqApp extends AbstractLinuxProcess implements LinuxProcess {
|
|||||||
private final boolean useLocalhostForP2P;
|
private final boolean useLocalhostForP2P;
|
||||||
public final boolean useDevPrivilegeKeys;
|
public final boolean useDevPrivilegeKeys;
|
||||||
private final String findBisqPidScript;
|
private final String findBisqPidScript;
|
||||||
|
private final String debugOpts;
|
||||||
|
|
||||||
public BisqApp(BisqAppConfig bisqAppConfig, ApiTestConfig config) {
|
public BisqApp(BisqAppConfig bisqAppConfig, ApiTestConfig config) {
|
||||||
super(bisqAppConfig.appName, config);
|
super(bisqAppConfig.appName, config);
|
||||||
@ -67,6 +68,9 @@ public class BisqApp extends AbstractLinuxProcess implements LinuxProcess {
|
|||||||
this.useDevPrivilegeKeys = true;
|
this.useDevPrivilegeKeys = true;
|
||||||
this.findBisqPidScript = (config.isRunningTest ? "." : "./apitest")
|
this.findBisqPidScript = (config.isRunningTest ? "." : "./apitest")
|
||||||
+ "/scripts/get-bisq-pid.sh";
|
+ "/scripts/get-bisq-pid.sh";
|
||||||
|
this.debugOpts = config.enableBisqDebugging
|
||||||
|
? " -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:" + bisqAppConfig.remoteDebugPort
|
||||||
|
: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -112,7 +116,6 @@ public class BisqApp extends AbstractLinuxProcess implements LinuxProcess {
|
|||||||
|
|
||||||
if (isAlive(pid)) {
|
if (isAlive(pid)) {
|
||||||
this.shutdownExceptions.add(new IllegalStateException(format("%s shutdown did not work", bisqAppConfig.appName)));
|
this.shutdownExceptions.add(new IllegalStateException(format("%s shutdown did not work", bisqAppConfig.appName)));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -209,7 +212,7 @@ public class BisqApp extends AbstractLinuxProcess implements LinuxProcess {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getJavaOptsSpec() {
|
private String getJavaOptsSpec() {
|
||||||
return "export JAVA_OPTS=" + bisqAppConfig.javaOpts + "; ";
|
return "export JAVA_OPTS=\"" + bisqAppConfig.javaOpts + debugOpts + "\"; ";
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> getOptsList() {
|
private List<String> getOptsList() {
|
||||||
|
@ -28,6 +28,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
|||||||
|
|
||||||
import bisq.apitest.config.ApiTestConfig;
|
import bisq.apitest.config.ApiTestConfig;
|
||||||
import bisq.apitest.method.BitcoinCliHelper;
|
import bisq.apitest.method.BitcoinCliHelper;
|
||||||
|
import bisq.cli.GrpcStubs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for all test types: 'method', 'scenario' and 'e2e'.
|
* Base class for all test types: 'method', 'scenario' and 'e2e'.
|
||||||
@ -65,19 +66,19 @@ public class ApiTestCase {
|
|||||||
|
|
||||||
public static void setUpScaffold(String supportingApps)
|
public static void setUpScaffold(String supportingApps)
|
||||||
throws InterruptedException, ExecutionException, IOException {
|
throws InterruptedException, ExecutionException, IOException {
|
||||||
// The supportingApps argument is a comma delimited string of supporting app
|
|
||||||
// names, e.g. "bitcoind,seednode,arbdaemon,alicedaemon,bobdaemon"
|
|
||||||
scaffold = new Scaffold(supportingApps).setUp();
|
scaffold = new Scaffold(supportingApps).setUp();
|
||||||
config = scaffold.config;
|
config = scaffold.config;
|
||||||
bitcoinCli = new BitcoinCliHelper((config));
|
bitcoinCli = new BitcoinCliHelper((config));
|
||||||
grpcStubs = new GrpcStubs(alicedaemon, config).init();
|
// For now, all grpc requests are sent to the alicedaemon, but this will need to
|
||||||
|
// be made configurable for new test cases that call arb or bob node daemons.
|
||||||
|
grpcStubs = new GrpcStubs("localhost", alicedaemon.apiPort, config.apiPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setUpScaffold()
|
public static void setUpScaffold(String[] params)
|
||||||
throws InterruptedException, ExecutionException, IOException {
|
throws InterruptedException, ExecutionException, IOException {
|
||||||
scaffold = new Scaffold(new String[]{}).setUp();
|
scaffold = new Scaffold(params).setUp();
|
||||||
config = scaffold.config;
|
config = scaffold.config;
|
||||||
grpcStubs = new GrpcStubs(alicedaemon, config).init();
|
grpcStubs = new GrpcStubs("localhost", alicedaemon.apiPort, config.apiPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void tearDownScaffold() {
|
public static void tearDownScaffold() {
|
||||||
|
@ -1,109 +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.apitest;
|
|
||||||
|
|
||||||
import bisq.proto.grpc.GetVersionGrpc;
|
|
||||||
import bisq.proto.grpc.OffersGrpc;
|
|
||||||
import bisq.proto.grpc.PaymentAccountsGrpc;
|
|
||||||
import bisq.proto.grpc.WalletsGrpc;
|
|
||||||
|
|
||||||
import io.grpc.CallCredentials;
|
|
||||||
import io.grpc.ManagedChannelBuilder;
|
|
||||||
import io.grpc.Metadata;
|
|
||||||
|
|
||||||
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;
|
|
||||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import bisq.apitest.config.ApiTestConfig;
|
|
||||||
import bisq.apitest.config.BisqAppConfig;
|
|
||||||
|
|
||||||
public class GrpcStubs {
|
|
||||||
|
|
||||||
public final CallCredentials credentials;
|
|
||||||
public final String host;
|
|
||||||
public final int port;
|
|
||||||
|
|
||||||
public GetVersionGrpc.GetVersionBlockingStub versionService;
|
|
||||||
public OffersGrpc.OffersBlockingStub offersService;
|
|
||||||
public PaymentAccountsGrpc.PaymentAccountsBlockingStub paymentAccountsService;
|
|
||||||
public WalletsGrpc.WalletsBlockingStub walletsService;
|
|
||||||
|
|
||||||
public GrpcStubs(BisqAppConfig bisqAppConfig, ApiTestConfig config) {
|
|
||||||
this.credentials = new PasswordCallCredentials(config.apiPassword);
|
|
||||||
this.host = "localhost";
|
|
||||||
this.port = bisqAppConfig.apiPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GrpcStubs init() {
|
|
||||||
var channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();
|
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
|
||||||
try {
|
|
||||||
channel.shutdown().awaitTermination(1, SECONDS);
|
|
||||||
} catch (InterruptedException ex) {
|
|
||||||
throw new IllegalStateException(ex);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.versionService = GetVersionGrpc.newBlockingStub(channel).withCallCredentials(credentials);
|
|
||||||
this.offersService = OffersGrpc.newBlockingStub(channel).withCallCredentials(credentials);
|
|
||||||
this.paymentAccountsService = PaymentAccountsGrpc.newBlockingStub(channel).withCallCredentials(credentials);
|
|
||||||
this.walletsService = WalletsGrpc.newBlockingStub(channel).withCallCredentials(credentials);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
static 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 = Metadata.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() {
|
|
||||||
// An experimental api. A noop but never called; tries to make it clearer to
|
|
||||||
// implementors that they may break in the future.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,9 @@
|
|||||||
|
package bisq.asset;
|
||||||
|
|
||||||
|
public class LiquidBitcoinAddressValidator extends RegexAddressValidator {
|
||||||
|
static private final String REGEX = "^([a-km-zA-HJ-NP-Z1-9]{26,35}|[a-km-zA-HJ-NP-Z1-9]{80}|[a-z]{2,5}1[ac-hj-np-z02-9]{8,87}|[A-Z]{2,5}1[AC-HJ-NP-Z02-9]{8,87})$";
|
||||||
|
|
||||||
|
public LiquidBitcoinAddressValidator() {
|
||||||
|
super(REGEX, "validation.altcoin.liquidBitcoin.invalidAddress");
|
||||||
|
}
|
||||||
|
}
|
@ -19,12 +19,12 @@ package bisq.asset.coins;
|
|||||||
|
|
||||||
import bisq.asset.AltCoinAccountDisclaimer;
|
import bisq.asset.AltCoinAccountDisclaimer;
|
||||||
import bisq.asset.Coin;
|
import bisq.asset.Coin;
|
||||||
import bisq.asset.RegexAddressValidator;
|
import bisq.asset.LiquidBitcoinAddressValidator;
|
||||||
|
|
||||||
@AltCoinAccountDisclaimer("account.altcoin.popup.liquidbitcoin.msg")
|
@AltCoinAccountDisclaimer("account.altcoin.popup.liquidbitcoin.msg")
|
||||||
public class LiquidBitcoin extends Coin {
|
public class LiquidBitcoin extends Coin {
|
||||||
|
|
||||||
public LiquidBitcoin() {
|
public LiquidBitcoin() {
|
||||||
super("Liquid Bitcoin", "L-BTC", new RegexAddressValidator("^([a-km-zA-HJ-NP-Z1-9]{26,35}|[a-km-zA-HJ-NP-Z1-9]{80}|[a-z]{2,5}1[ac-hj-np-z02-9]{8,87}|[A-Z]{2,5}1[AC-HJ-NP-Z02-9]{8,87})$", "validation.altcoin.liquidBitcoin.invalidAddress"));
|
super("Liquid Bitcoin", "L-BTC", new LiquidBitcoinAddressValidator());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
12
assets/src/main/java/bisq/asset/coins/TetherUSDLiquid.java
Normal file
12
assets/src/main/java/bisq/asset/coins/TetherUSDLiquid.java
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package bisq.asset.coins;
|
||||||
|
|
||||||
|
import bisq.asset.Coin;
|
||||||
|
import bisq.asset.LiquidBitcoinAddressValidator;
|
||||||
|
|
||||||
|
public class TetherUSDLiquid extends Coin {
|
||||||
|
public TetherUSDLiquid() {
|
||||||
|
// If you add a new USDT variant or want to change this ticker symbol you should also look here:
|
||||||
|
// core/src/main/java/bisq/core/provider/price/PriceProvider.java:getAll()
|
||||||
|
super("Tether USD (Liquid Bitcoin)", "L-USDT", new LiquidBitcoinAddressValidator());
|
||||||
|
}
|
||||||
|
}
|
12
assets/src/main/java/bisq/asset/coins/TetherUSDOmni.java
Normal file
12
assets/src/main/java/bisq/asset/coins/TetherUSDOmni.java
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package bisq.asset.coins;
|
||||||
|
|
||||||
|
import bisq.asset.Base58BitcoinAddressValidator;
|
||||||
|
import bisq.asset.Coin;
|
||||||
|
|
||||||
|
public class TetherUSDOmni extends Coin {
|
||||||
|
public TetherUSDOmni() {
|
||||||
|
// If you add a new USDT variant or want to change this ticker symbol you should also look here:
|
||||||
|
// core/src/main/java/bisq/core/provider/price/PriceProvider.java:getAll()
|
||||||
|
super("Tether USD (Omni)", "USDT-O", new Base58BitcoinAddressValidator());
|
||||||
|
}
|
||||||
|
}
|
11
assets/src/main/java/bisq/asset/tokens/TetherUSDERC20.java
Normal file
11
assets/src/main/java/bisq/asset/tokens/TetherUSDERC20.java
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package bisq.asset.tokens;
|
||||||
|
|
||||||
|
import bisq.asset.Erc20Token;
|
||||||
|
|
||||||
|
public class TetherUSDERC20 extends Erc20Token {
|
||||||
|
public TetherUSDERC20() {
|
||||||
|
// If you add a new USDT variant or want to change this ticker symbol you should also look here:
|
||||||
|
// core/src/main/java/bisq/core/provider/price/PriceProvider.java:getAll()
|
||||||
|
super("Tether USD (ERC20)", "USDT-E");
|
||||||
|
}
|
||||||
|
}
|
@ -104,6 +104,8 @@ bisq.asset.coins.Spectrecoin
|
|||||||
bisq.asset.coins.Starwels
|
bisq.asset.coins.Starwels
|
||||||
bisq.asset.coins.SUB1X
|
bisq.asset.coins.SUB1X
|
||||||
bisq.asset.coins.TEO
|
bisq.asset.coins.TEO
|
||||||
|
bisq.asset.coins.TetherUSDLiquid
|
||||||
|
bisq.asset.coins.TetherUSDOmni
|
||||||
bisq.asset.coins.TurtleCoin
|
bisq.asset.coins.TurtleCoin
|
||||||
bisq.asset.coins.UnitedCommunityCoin
|
bisq.asset.coins.UnitedCommunityCoin
|
||||||
bisq.asset.coins.Unobtanium
|
bisq.asset.coins.Unobtanium
|
||||||
@ -123,6 +125,7 @@ bisq.asset.coins.ZeroClassic
|
|||||||
bisq.asset.tokens.AugmintEuro
|
bisq.asset.tokens.AugmintEuro
|
||||||
bisq.asset.tokens.DaiStablecoin
|
bisq.asset.tokens.DaiStablecoin
|
||||||
bisq.asset.tokens.EtherStone
|
bisq.asset.tokens.EtherStone
|
||||||
|
bisq.asset.tokens.TetherUSDERC20
|
||||||
bisq.asset.tokens.TrueUSD
|
bisq.asset.tokens.TrueUSD
|
||||||
bisq.asset.tokens.USDCoin
|
bisq.asset.tokens.USDCoin
|
||||||
bisq.asset.tokens.VectorspaceAI
|
bisq.asset.tokens.VectorspaceAI
|
||||||
|
@ -51,7 +51,7 @@ configure(subprojects) {
|
|||||||
javaxAnnotationVersion = '1.2'
|
javaxAnnotationVersion = '1.2'
|
||||||
jcsvVersion = '1.4.0'
|
jcsvVersion = '1.4.0'
|
||||||
jetbrainsAnnotationsVersion = '13.0'
|
jetbrainsAnnotationsVersion = '13.0'
|
||||||
jfoenixVersion = '9.0.6'
|
jfoenixVersion = '9.0.10'
|
||||||
joptVersion = '5.0.4'
|
joptVersion = '5.0.4'
|
||||||
jsonsimpleVersion = '1.1.1'
|
jsonsimpleVersion = '1.1.1'
|
||||||
junitVersion = '4.12'
|
junitVersion = '4.12'
|
||||||
|
@ -23,17 +23,12 @@ import bisq.proto.grpc.GetBalanceRequest;
|
|||||||
import bisq.proto.grpc.GetFundingAddressesRequest;
|
import bisq.proto.grpc.GetFundingAddressesRequest;
|
||||||
import bisq.proto.grpc.GetOffersRequest;
|
import bisq.proto.grpc.GetOffersRequest;
|
||||||
import bisq.proto.grpc.GetPaymentAccountsRequest;
|
import bisq.proto.grpc.GetPaymentAccountsRequest;
|
||||||
import bisq.proto.grpc.GetVersionGrpc;
|
|
||||||
import bisq.proto.grpc.GetVersionRequest;
|
import bisq.proto.grpc.GetVersionRequest;
|
||||||
import bisq.proto.grpc.LockWalletRequest;
|
import bisq.proto.grpc.LockWalletRequest;
|
||||||
import bisq.proto.grpc.OffersGrpc;
|
|
||||||
import bisq.proto.grpc.PaymentAccountsGrpc;
|
|
||||||
import bisq.proto.grpc.RemoveWalletPasswordRequest;
|
import bisq.proto.grpc.RemoveWalletPasswordRequest;
|
||||||
import bisq.proto.grpc.SetWalletPasswordRequest;
|
import bisq.proto.grpc.SetWalletPasswordRequest;
|
||||||
import bisq.proto.grpc.UnlockWalletRequest;
|
import bisq.proto.grpc.UnlockWalletRequest;
|
||||||
import bisq.proto.grpc.WalletsGrpc;
|
|
||||||
|
|
||||||
import io.grpc.ManagedChannelBuilder;
|
|
||||||
import io.grpc.StatusRuntimeException;
|
import io.grpc.StatusRuntimeException;
|
||||||
|
|
||||||
import joptsimple.OptionParser;
|
import joptsimple.OptionParser;
|
||||||
@ -43,7 +38,6 @@ import java.io.IOException;
|
|||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@ -133,21 +127,11 @@ public class CliMain {
|
|||||||
if (password == null)
|
if (password == null)
|
||||||
throw new IllegalArgumentException("missing required 'password' option");
|
throw new IllegalArgumentException("missing required 'password' option");
|
||||||
|
|
||||||
var credentials = new PasswordCallCredentials(password);
|
GrpcStubs grpcStubs = new GrpcStubs(host, port, password);
|
||||||
|
var versionService = grpcStubs.versionService;
|
||||||
var channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();
|
var offersService = grpcStubs.offersService;
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
var paymentAccountsService = grpcStubs.paymentAccountsService;
|
||||||
try {
|
var walletsService = grpcStubs.walletsService;
|
||||||
channel.shutdown().awaitTermination(1, TimeUnit.SECONDS);
|
|
||||||
} catch (InterruptedException ex) {
|
|
||||||
throw new RuntimeException(ex);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
var versionService = GetVersionGrpc.newBlockingStub(channel).withCallCredentials(credentials);
|
|
||||||
var offersService = OffersGrpc.newBlockingStub(channel).withCallCredentials(credentials);
|
|
||||||
var paymentAccountsService = PaymentAccountsGrpc.newBlockingStub(channel).withCallCredentials(credentials);
|
|
||||||
var walletsService = WalletsGrpc.newBlockingStub(channel).withCallCredentials(credentials);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
switch (method) {
|
switch (method) {
|
||||||
|
54
cli/src/main/java/bisq/cli/GrpcStubs.java
Normal file
54
cli/src/main/java/bisq/cli/GrpcStubs.java
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* 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.GetVersionGrpc;
|
||||||
|
import bisq.proto.grpc.OffersGrpc;
|
||||||
|
import bisq.proto.grpc.PaymentAccountsGrpc;
|
||||||
|
import bisq.proto.grpc.WalletsGrpc;
|
||||||
|
|
||||||
|
import io.grpc.CallCredentials;
|
||||||
|
import io.grpc.ManagedChannelBuilder;
|
||||||
|
|
||||||
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
|
||||||
|
public class GrpcStubs {
|
||||||
|
|
||||||
|
public final GetVersionGrpc.GetVersionBlockingStub versionService;
|
||||||
|
public final OffersGrpc.OffersBlockingStub offersService;
|
||||||
|
public final PaymentAccountsGrpc.PaymentAccountsBlockingStub paymentAccountsService;
|
||||||
|
public final WalletsGrpc.WalletsBlockingStub walletsService;
|
||||||
|
|
||||||
|
public GrpcStubs(String apiHost, int apiPort, String apiPassword) {
|
||||||
|
CallCredentials credentials = new PasswordCallCredentials(apiPassword);
|
||||||
|
|
||||||
|
var channel = ManagedChannelBuilder.forAddress(apiHost, apiPort).usePlaintext().build();
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||||
|
try {
|
||||||
|
channel.shutdown().awaitTermination(1, SECONDS);
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
throw new IllegalStateException(ex);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.versionService = GetVersionGrpc.newBlockingStub(channel).withCallCredentials(credentials);
|
||||||
|
this.offersService = OffersGrpc.newBlockingStub(channel).withCallCredentials(credentials);
|
||||||
|
this.paymentAccountsService = PaymentAccountsGrpc.newBlockingStub(channel).withCallCredentials(credentials);
|
||||||
|
this.walletsService = WalletsGrpc.newBlockingStub(channel).withCallCredentials(credentials);
|
||||||
|
}
|
||||||
|
}
|
@ -18,12 +18,15 @@
|
|||||||
package bisq.common.proto;
|
package bisq.common.proto;
|
||||||
|
|
||||||
import bisq.common.Proto;
|
import bisq.common.Proto;
|
||||||
|
import bisq.common.util.CollectionUtils;
|
||||||
|
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
import com.google.protobuf.Message;
|
import com.google.protobuf.Message;
|
||||||
|
import com.google.protobuf.ProtocolStringList;
|
||||||
|
|
||||||
import com.google.common.base.Enums;
|
import com.google.common.base.Enums;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@ -101,4 +104,9 @@ public class ProtoUtil {
|
|||||||
Function<? super Message, T> extra) {
|
Function<? super Message, T> extra) {
|
||||||
return collection.stream().map(o -> extra.apply(o.toProtoMessage())).collect(Collectors.toList());
|
return collection.stream().map(o -> extra.apply(o.toProtoMessage())).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<String> protocolStringListToList(ProtocolStringList protocolStringList) {
|
||||||
|
return CollectionUtils.isEmpty(protocolStringList) ? new ArrayList<>() : new ArrayList<>(protocolStringList);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -180,7 +180,7 @@ public class SignedWitnessService {
|
|||||||
public boolean isFilteredWitness(AccountAgeWitness accountAgeWitness) {
|
public boolean isFilteredWitness(AccountAgeWitness accountAgeWitness) {
|
||||||
return getSignedWitnessSet(accountAgeWitness).stream()
|
return getSignedWitnessSet(accountAgeWitness).stream()
|
||||||
.map(SignedWitness::getWitnessOwnerPubKey)
|
.map(SignedWitness::getWitnessOwnerPubKey)
|
||||||
.anyMatch(ownerPubKey -> filterManager.isSignerPubKeyBanned(Utils.HEX.encode(ownerPubKey)));
|
.anyMatch(ownerPubKey -> filterManager.isWitnessSignerPubKeyBanned(Utils.HEX.encode(ownerPubKey)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] ownerPubKey(AccountAgeWitness accountAgeWitness) {
|
private byte[] ownerPubKey(AccountAgeWitness accountAgeWitness) {
|
||||||
@ -442,7 +442,7 @@ public class SignedWitnessService {
|
|||||||
private boolean isValidSignerWitnessInternal(SignedWitness signedWitness,
|
private boolean isValidSignerWitnessInternal(SignedWitness signedWitness,
|
||||||
long childSignedWitnessDateMillis,
|
long childSignedWitnessDateMillis,
|
||||||
Stack<P2PDataStorage.ByteArray> excludedPubKeys) {
|
Stack<P2PDataStorage.ByteArray> excludedPubKeys) {
|
||||||
if (filterManager.isSignerPubKeyBanned(Utils.HEX.encode(signedWitness.getWitnessOwnerPubKey()))) {
|
if (filterManager.isWitnessSignerPubKeyBanned(Utils.HEX.encode(signedWitness.getWitnessOwnerPubKey()))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!verifySignature(signedWitness)) {
|
if (!verifySignature(signedWitness)) {
|
||||||
|
@ -718,13 +718,13 @@ public class AccountAgeWitnessService {
|
|||||||
filterManager.isCurrencyBanned(dispute.getContract().getOfferPayload().getCurrencyCode()) ||
|
filterManager.isCurrencyBanned(dispute.getContract().getOfferPayload().getCurrencyCode()) ||
|
||||||
filterManager.isPaymentMethodBanned(
|
filterManager.isPaymentMethodBanned(
|
||||||
PaymentMethod.getPaymentMethodById(dispute.getContract().getPaymentMethodId())) ||
|
PaymentMethod.getPaymentMethodById(dispute.getContract().getPaymentMethodId())) ||
|
||||||
filterManager.isPeersPaymentAccountDataAreBanned(dispute.getContract().getBuyerPaymentAccountPayload(),
|
filterManager.arePeersPaymentAccountDataBanned(dispute.getContract().getBuyerPaymentAccountPayload(),
|
||||||
new PaymentAccountFilter[1]) ||
|
new PaymentAccountFilter[1]) ||
|
||||||
filterManager.isPeersPaymentAccountDataAreBanned(dispute.getContract().getSellerPaymentAccountPayload(),
|
filterManager.arePeersPaymentAccountDataBanned(dispute.getContract().getSellerPaymentAccountPayload(),
|
||||||
new PaymentAccountFilter[1]) ||
|
new PaymentAccountFilter[1]) ||
|
||||||
filterManager.isSignerPubKeyBanned(
|
filterManager.isWitnessSignerPubKeyBanned(
|
||||||
Utils.HEX.encode(dispute.getContract().getBuyerPubKeyRing().getSignaturePubKeyBytes())) ||
|
Utils.HEX.encode(dispute.getContract().getBuyerPubKeyRing().getSignaturePubKeyBytes())) ||
|
||||||
filterManager.isSignerPubKeyBanned(
|
filterManager.isWitnessSignerPubKeyBanned(
|
||||||
Utils.HEX.encode(dispute.getContract().getSellerPubKeyRing().getSignaturePubKeyBytes()));
|
Utils.HEX.encode(dispute.getContract().getSellerPubKeyRing().getSignaturePubKeyBytes()));
|
||||||
return !isFiltered;
|
return !isFiltered;
|
||||||
}
|
}
|
||||||
|
@ -96,6 +96,7 @@ public class BisqHeadlessApp implements HeadlessApp {
|
|||||||
bisqSetup.setVoteResultExceptionHandler(voteResultException -> log.warn("voteResultException={}", voteResultException.toString()));
|
bisqSetup.setVoteResultExceptionHandler(voteResultException -> log.warn("voteResultException={}", voteResultException.toString()));
|
||||||
bisqSetup.setRejectedTxErrorMessageHandler(errorMessage -> log.warn("setRejectedTxErrorMessageHandler. errorMessage={}", errorMessage));
|
bisqSetup.setRejectedTxErrorMessageHandler(errorMessage -> log.warn("setRejectedTxErrorMessageHandler. errorMessage={}", errorMessage));
|
||||||
bisqSetup.setShowPopupIfInvalidBtcConfigHandler(() -> log.error("onShowPopupIfInvalidBtcConfigHandler"));
|
bisqSetup.setShowPopupIfInvalidBtcConfigHandler(() -> log.error("onShowPopupIfInvalidBtcConfigHandler"));
|
||||||
|
bisqSetup.setRevolutAccountsUpdateHandler(revolutAccountList -> log.info("setRevolutAccountsUpdateHandler: revolutAccountList={}", revolutAccountList));
|
||||||
|
|
||||||
//TODO move to bisqSetup
|
//TODO move to bisqSetup
|
||||||
corruptedDatabaseFilesHandler.getCorruptedDatabaseFiles().ifPresent(files -> log.warn("getCorruptedDatabaseFiles. files={}", files));
|
corruptedDatabaseFilesHandler.getCorruptedDatabaseFiles().ifPresent(files -> log.warn("getCorruptedDatabaseFiles. files={}", files));
|
||||||
|
@ -44,6 +44,7 @@ import bisq.core.notifications.alerts.market.MarketAlerts;
|
|||||||
import bisq.core.notifications.alerts.price.PriceAlert;
|
import bisq.core.notifications.alerts.price.PriceAlert;
|
||||||
import bisq.core.offer.OpenOfferManager;
|
import bisq.core.offer.OpenOfferManager;
|
||||||
import bisq.core.payment.PaymentAccount;
|
import bisq.core.payment.PaymentAccount;
|
||||||
|
import bisq.core.payment.RevolutAccount;
|
||||||
import bisq.core.payment.TradeLimits;
|
import bisq.core.payment.TradeLimits;
|
||||||
import bisq.core.payment.payload.PaymentMethod;
|
import bisq.core.payment.payload.PaymentMethod;
|
||||||
import bisq.core.provider.fee.FeeService;
|
import bisq.core.provider.fee.FeeService;
|
||||||
@ -221,6 +222,9 @@ public class BisqSetup {
|
|||||||
@Setter
|
@Setter
|
||||||
@Nullable
|
@Nullable
|
||||||
private Runnable showPopupIfInvalidBtcConfigHandler;
|
private Runnable showPopupIfInvalidBtcConfigHandler;
|
||||||
|
@Setter
|
||||||
|
@Nullable
|
||||||
|
private Consumer<List<RevolutAccount>> revolutAccountsUpdateHandler;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
final BooleanProperty newVersionAvailableProperty = new SimpleBooleanProperty(false);
|
final BooleanProperty newVersionAvailableProperty = new SimpleBooleanProperty(false);
|
||||||
@ -824,6 +828,8 @@ public class BisqSetup {
|
|||||||
priceAlert.onAllServicesInitialized();
|
priceAlert.onAllServicesInitialized();
|
||||||
marketAlerts.onAllServicesInitialized();
|
marketAlerts.onAllServicesInitialized();
|
||||||
|
|
||||||
|
user.onAllServicesInitialized(revolutAccountsUpdateHandler);
|
||||||
|
|
||||||
allBasicServicesInitialized = true;
|
allBasicServicesInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,11 +164,13 @@ public abstract class ExecutableForAppWithP2p extends BisqExecutable implements
|
|||||||
UserThread.runAfter(() -> {
|
UserThread.runAfter(() -> {
|
||||||
// We check every hour if we are in the target hour.
|
// We check every hour if we are in the target hour.
|
||||||
UserThread.runPeriodically(() -> {
|
UserThread.runPeriodically(() -> {
|
||||||
int currentHour = ZonedDateTime.ofInstant(Instant.now(), ZoneId.of("GMT0")).getHour();
|
int currentHour = ZonedDateTime.ofInstant(Instant.now(), ZoneId.of("UTC")).getHour();
|
||||||
if (currentHour == target) {
|
if (currentHour == target) {
|
||||||
log.warn("\n\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n" +
|
log.warn("\n\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n" +
|
||||||
"Shut down node at hour {}" +
|
"Shut down node at hour {} (UTC time is {})" +
|
||||||
"\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n\n", target);
|
"\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n\n",
|
||||||
|
target,
|
||||||
|
ZonedDateTime.ofInstant(Instant.now(), ZoneId.of("UTC")).toString());
|
||||||
shutDown(gracefulShutDownHandler);
|
shutDown(gracefulShutDownHandler);
|
||||||
}
|
}
|
||||||
}, TimeUnit.MINUTES.toSeconds(10));
|
}, TimeUnit.MINUTES.toSeconds(10));
|
||||||
|
@ -156,7 +156,7 @@ public abstract class StateNetworkService<Msg extends NewStateHashMessage,
|
|||||||
|
|
||||||
public void broadcastMyStateHash(StH myStateHash) {
|
public void broadcastMyStateHash(StH myStateHash) {
|
||||||
NewStateHashMessage newStateHashMessage = getNewStateHashMessage(myStateHash);
|
NewStateHashMessage newStateHashMessage = getNewStateHashMessage(myStateHash);
|
||||||
broadcaster.broadcast(newStateHashMessage, networkNode.getNodeAddress(), null);
|
broadcaster.broadcast(newStateHashMessage, networkNode.getNodeAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void requestHashes(int fromHeight, String peersAddress) {
|
public void requestHashes(int fromHeight, String peersAddress) {
|
||||||
|
@ -105,7 +105,7 @@ public class FullNodeNetworkService implements MessageListener, PeerManager.List
|
|||||||
log.info("Publish new block at height={} and block hash={}", block.getHeight(), block.getHash());
|
log.info("Publish new block at height={} and block hash={}", block.getHeight(), block.getHash());
|
||||||
RawBlock rawBlock = RawBlock.fromBlock(block);
|
RawBlock rawBlock = RawBlock.fromBlock(block);
|
||||||
NewBlockBroadcastMessage newBlockBroadcastMessage = new NewBlockBroadcastMessage(rawBlock);
|
NewBlockBroadcastMessage newBlockBroadcastMessage = new NewBlockBroadcastMessage(rawBlock);
|
||||||
broadcaster.broadcast(newBlockBroadcastMessage, networkNode.getNodeAddress(), null);
|
broadcaster.broadcast(newBlockBroadcastMessage, networkNode.getNodeAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -238,7 +238,7 @@ public class LiteNodeNetworkService implements MessageListener, ConnectionListen
|
|||||||
log.debug("We received a new message from peer {} and broadcast it to our peers. extBlockId={}",
|
log.debug("We received a new message from peer {} and broadcast it to our peers. extBlockId={}",
|
||||||
connection.getPeersNodeAddressOptional().orElse(null), extBlockId);
|
connection.getPeersNodeAddressOptional().orElse(null), extBlockId);
|
||||||
receivedBlocks.add(extBlockId);
|
receivedBlocks.add(extBlockId);
|
||||||
broadcaster.broadcast(newBlockBroadcastMessage, connection.getPeersNodeAddressOptional().orElse(null), null);
|
broadcaster.broadcast(newBlockBroadcastMessage, connection.getPeersNodeAddressOptional().orElse(null));
|
||||||
listeners.forEach(listener -> listener.onNewBlockReceived(newBlockBroadcastMessage));
|
listeners.forEach(listener -> listener.onNewBlockReceived(newBlockBroadcastMessage));
|
||||||
} else {
|
} else {
|
||||||
log.debug("We had that message already and do not further broadcast it. extBlockId={}", extBlockId);
|
log.debug("We had that message already and do not further broadcast it. extBlockId={}", extBlockId);
|
||||||
|
@ -21,8 +21,10 @@ import bisq.network.p2p.storage.payload.ExpirablePayload;
|
|||||||
import bisq.network.p2p.storage.payload.ProtectedStoragePayload;
|
import bisq.network.p2p.storage.payload.ProtectedStoragePayload;
|
||||||
|
|
||||||
import bisq.common.crypto.Sig;
|
import bisq.common.crypto.Sig;
|
||||||
|
import bisq.common.proto.ProtoUtil;
|
||||||
import bisq.common.util.CollectionUtils;
|
import bisq.common.util.CollectionUtils;
|
||||||
import bisq.common.util.ExtraDataMapValidator;
|
import bisq.common.util.ExtraDataMapValidator;
|
||||||
|
import bisq.common.util.Utilities;
|
||||||
|
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
|
|
||||||
@ -30,152 +32,140 @@ import com.google.common.annotations.VisibleForTesting;
|
|||||||
|
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.Value;
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.ToString;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Getter
|
@Value
|
||||||
@EqualsAndHashCode
|
|
||||||
@ToString
|
|
||||||
public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
||||||
private final List<String> bannedOfferIds;
|
private final List<String> bannedOfferIds;
|
||||||
private final List<String> bannedNodeAddress;
|
private final List<String> bannedNodeAddress;
|
||||||
private final List<PaymentAccountFilter> bannedPaymentAccounts;
|
private final List<PaymentAccountFilter> bannedPaymentAccounts;
|
||||||
|
|
||||||
// Because we added those fields in v 0.5.4 and old versions do not have it we annotate it with @Nullable
|
|
||||||
@Nullable
|
|
||||||
private final List<String> bannedCurrencies;
|
private final List<String> bannedCurrencies;
|
||||||
@Nullable
|
|
||||||
private final List<String> bannedPaymentMethods;
|
private final List<String> bannedPaymentMethods;
|
||||||
|
|
||||||
// added in v0.6.0
|
|
||||||
@Nullable
|
|
||||||
private final List<String> arbitrators;
|
private final List<String> arbitrators;
|
||||||
@Nullable
|
|
||||||
private final List<String> seedNodes;
|
private final List<String> seedNodes;
|
||||||
@Nullable
|
|
||||||
private final List<String> priceRelayNodes;
|
private final List<String> priceRelayNodes;
|
||||||
private final boolean preventPublicBtcNetwork;
|
private final boolean preventPublicBtcNetwork;
|
||||||
|
|
||||||
// added in v0.6.2
|
|
||||||
@Nullable
|
|
||||||
private final List<String> btcNodes;
|
private final List<String> btcNodes;
|
||||||
|
// SignatureAsBase64 is not set initially as we use the serialized data for signing. We set it after signature is
|
||||||
|
// created by cloning the object with a non-null sig.
|
||||||
|
@Nullable
|
||||||
|
private final String signatureAsBase64;
|
||||||
|
// The pub EC key from the dev who has signed and published the filter (different to ownerPubKeyBytes)
|
||||||
|
private final String signerPubKeyAsHex;
|
||||||
|
|
||||||
|
// The pub key used for the data protection in the p2p storage
|
||||||
|
private final byte[] ownerPubKeyBytes;
|
||||||
|
private final boolean disableDao;
|
||||||
|
private final String disableDaoBelowVersion;
|
||||||
|
private final String disableTradeBelowVersion;
|
||||||
|
private final List<String> mediators;
|
||||||
|
private final List<String> refundAgents;
|
||||||
|
|
||||||
|
private final List<String> bannedAccountWitnessSignerPubKeys;
|
||||||
|
|
||||||
|
private final List<String> btcFeeReceiverAddresses;
|
||||||
|
|
||||||
|
private final long creationDate;
|
||||||
|
|
||||||
|
private final List<String> bannedPrivilegedDevPubKeys;
|
||||||
|
|
||||||
private String signatureAsBase64;
|
|
||||||
private byte[] ownerPubKeyBytes;
|
|
||||||
// Should be only used in emergency case if we need to add data but do not want to break backward compatibility
|
// Should be only used in emergency case if we need to add data but do not want to break backward compatibility
|
||||||
// at the P2P network storage checks. The hash of the object will be used to verify if the data is valid. Any new
|
// at the P2P network storage checks. The hash of the object will be used to verify if the data is valid. Any new
|
||||||
// field in a class would break that hash and therefore break the storage mechanism.
|
// field in a class would break that hash and therefore break the storage mechanism.
|
||||||
@Nullable
|
@Nullable
|
||||||
private Map<String, String> extraDataMap;
|
private Map<String, String> extraDataMap;
|
||||||
private PublicKey ownerPubKey;
|
|
||||||
|
|
||||||
// added in v0.9.4
|
private transient PublicKey ownerPubKey;
|
||||||
private final boolean disableDao;
|
|
||||||
|
|
||||||
// added in v0.9.8
|
// added at v1.3.8
|
||||||
@Nullable
|
|
||||||
private final String disableDaoBelowVersion;
|
|
||||||
@Nullable
|
|
||||||
private final String disableTradeBelowVersion;
|
|
||||||
|
|
||||||
// added in v1.1.6
|
|
||||||
@Nullable
|
|
||||||
private final List<String> mediators;
|
|
||||||
|
|
||||||
// added in v1.2.0
|
|
||||||
@Nullable
|
|
||||||
private final List<String> refundAgents;
|
|
||||||
|
|
||||||
// added in v1.2.x
|
|
||||||
@Nullable
|
|
||||||
private final List<String> bannedSignerPubKeys;
|
|
||||||
|
|
||||||
// added in v1.3.2
|
|
||||||
@Nullable
|
|
||||||
private final List<String> btcFeeReceiverAddresses;
|
|
||||||
|
|
||||||
// added after v1.3.7
|
|
||||||
private final boolean disableAutoConf;
|
private final boolean disableAutoConf;
|
||||||
|
|
||||||
public Filter(List<String> bannedOfferIds,
|
// After we have created the signature from the filter data we clone it and apply the signature
|
||||||
List<String> bannedNodeAddress,
|
static Filter cloneWithSig(Filter filter, String signatureAsBase64) {
|
||||||
List<PaymentAccountFilter> bannedPaymentAccounts,
|
return new Filter(filter.getBannedOfferIds(),
|
||||||
@Nullable List<String> bannedCurrencies,
|
filter.getBannedNodeAddress(),
|
||||||
@Nullable List<String> bannedPaymentMethods,
|
filter.getBannedPaymentAccounts(),
|
||||||
@Nullable List<String> arbitrators,
|
filter.getBannedCurrencies(),
|
||||||
@Nullable List<String> seedNodes,
|
filter.getBannedPaymentMethods(),
|
||||||
@Nullable List<String> priceRelayNodes,
|
filter.getArbitrators(),
|
||||||
boolean preventPublicBtcNetwork,
|
filter.getSeedNodes(),
|
||||||
@Nullable List<String> btcNodes,
|
filter.getPriceRelayNodes(),
|
||||||
boolean disableDao,
|
filter.isPreventPublicBtcNetwork(),
|
||||||
@Nullable String disableDaoBelowVersion,
|
filter.getBtcNodes(),
|
||||||
@Nullable String disableTradeBelowVersion,
|
filter.isDisableDao(),
|
||||||
@Nullable List<String> mediators,
|
filter.getDisableDaoBelowVersion(),
|
||||||
@Nullable List<String> refundAgents,
|
filter.getDisableTradeBelowVersion(),
|
||||||
@Nullable List<String> bannedSignerPubKeys,
|
filter.getMediators(),
|
||||||
@Nullable List<String> btcFeeReceiverAddresses,
|
filter.getRefundAgents(),
|
||||||
boolean disableAutoConf) {
|
filter.getBannedAccountWitnessSignerPubKeys(),
|
||||||
this.bannedOfferIds = bannedOfferIds;
|
filter.getBtcFeeReceiverAddresses(),
|
||||||
this.bannedNodeAddress = bannedNodeAddress;
|
filter.getOwnerPubKeyBytes(),
|
||||||
this.bannedPaymentAccounts = bannedPaymentAccounts;
|
filter.getCreationDate(),
|
||||||
this.bannedCurrencies = bannedCurrencies;
|
filter.getExtraDataMap(),
|
||||||
this.bannedPaymentMethods = bannedPaymentMethods;
|
signatureAsBase64,
|
||||||
this.arbitrators = arbitrators;
|
filter.getSignerPubKeyAsHex(),
|
||||||
this.seedNodes = seedNodes;
|
filter.getBannedPrivilegedDevPubKeys(),
|
||||||
this.priceRelayNodes = priceRelayNodes;
|
filter.isDisableAutoConf());
|
||||||
this.preventPublicBtcNetwork = preventPublicBtcNetwork;
|
|
||||||
this.btcNodes = btcNodes;
|
|
||||||
this.disableDao = disableDao;
|
|
||||||
this.disableDaoBelowVersion = disableDaoBelowVersion;
|
|
||||||
this.disableTradeBelowVersion = disableTradeBelowVersion;
|
|
||||||
this.mediators = mediators;
|
|
||||||
this.refundAgents = refundAgents;
|
|
||||||
this.bannedSignerPubKeys = bannedSignerPubKeys;
|
|
||||||
this.btcFeeReceiverAddresses = btcFeeReceiverAddresses;
|
|
||||||
this.disableAutoConf = disableAutoConf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used for signature verification as we created the sig without the signatureAsBase64 field we set it to null again
|
||||||
|
static Filter cloneWithoutSig(Filter filter) {
|
||||||
|
return new Filter(filter.getBannedOfferIds(),
|
||||||
|
filter.getBannedNodeAddress(),
|
||||||
|
filter.getBannedPaymentAccounts(),
|
||||||
|
filter.getBannedCurrencies(),
|
||||||
|
filter.getBannedPaymentMethods(),
|
||||||
|
filter.getArbitrators(),
|
||||||
|
filter.getSeedNodes(),
|
||||||
|
filter.getPriceRelayNodes(),
|
||||||
|
filter.isPreventPublicBtcNetwork(),
|
||||||
|
filter.getBtcNodes(),
|
||||||
|
filter.isDisableDao(),
|
||||||
|
filter.getDisableDaoBelowVersion(),
|
||||||
|
filter.getDisableTradeBelowVersion(),
|
||||||
|
filter.getMediators(),
|
||||||
|
filter.getRefundAgents(),
|
||||||
|
filter.getBannedAccountWitnessSignerPubKeys(),
|
||||||
|
filter.getBtcFeeReceiverAddresses(),
|
||||||
|
filter.getOwnerPubKeyBytes(),
|
||||||
|
filter.getCreationDate(),
|
||||||
|
filter.getExtraDataMap(),
|
||||||
|
null,
|
||||||
|
filter.getSignerPubKeyAsHex(),
|
||||||
|
filter.getBannedPrivilegedDevPubKeys(),
|
||||||
|
filter.isDisableAutoConf());
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// PROTO BUFFER
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public Filter(List<String> bannedOfferIds,
|
public Filter(List<String> bannedOfferIds,
|
||||||
List<String> bannedNodeAddress,
|
List<String> bannedNodeAddress,
|
||||||
List<PaymentAccountFilter> bannedPaymentAccounts,
|
List<PaymentAccountFilter> bannedPaymentAccounts,
|
||||||
@Nullable List<String> bannedCurrencies,
|
List<String> bannedCurrencies,
|
||||||
@Nullable List<String> bannedPaymentMethods,
|
List<String> bannedPaymentMethods,
|
||||||
@Nullable List<String> arbitrators,
|
List<String> arbitrators,
|
||||||
@Nullable List<String> seedNodes,
|
List<String> seedNodes,
|
||||||
@Nullable List<String> priceRelayNodes,
|
List<String> priceRelayNodes,
|
||||||
boolean preventPublicBtcNetwork,
|
boolean preventPublicBtcNetwork,
|
||||||
@Nullable List<String> btcNodes,
|
List<String> btcNodes,
|
||||||
boolean disableDao,
|
boolean disableDao,
|
||||||
@Nullable String disableDaoBelowVersion,
|
String disableDaoBelowVersion,
|
||||||
@Nullable String disableTradeBelowVersion,
|
String disableTradeBelowVersion,
|
||||||
String signatureAsBase64,
|
List<String> mediators,
|
||||||
byte[] ownerPubKeyBytes,
|
List<String> refundAgents,
|
||||||
@Nullable Map<String, String> extraDataMap,
|
List<String> bannedAccountWitnessSignerPubKeys,
|
||||||
@Nullable List<String> mediators,
|
List<String> btcFeeReceiverAddresses,
|
||||||
@Nullable List<String> refundAgents,
|
PublicKey ownerPubKey,
|
||||||
@Nullable List<String> bannedSignerPubKeys,
|
String signerPubKeyAsHex,
|
||||||
@Nullable List<String> btcFeeReceiverAddresses,
|
List<String> bannedPrivilegedDevPubKeys,
|
||||||
boolean disableAutoConf) {
|
boolean disableAutoConf) {
|
||||||
this(bannedOfferIds,
|
this(bannedOfferIds,
|
||||||
bannedNodeAddress,
|
bannedNodeAddress,
|
||||||
@ -192,76 +182,146 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
|||||||
disableTradeBelowVersion,
|
disableTradeBelowVersion,
|
||||||
mediators,
|
mediators,
|
||||||
refundAgents,
|
refundAgents,
|
||||||
bannedSignerPubKeys,
|
bannedAccountWitnessSignerPubKeys,
|
||||||
btcFeeReceiverAddresses,
|
btcFeeReceiverAddresses,
|
||||||
|
Sig.getPublicKeyBytes(ownerPubKey),
|
||||||
|
System.currentTimeMillis(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
signerPubKeyAsHex,
|
||||||
|
bannedPrivilegedDevPubKeys,
|
||||||
disableAutoConf);
|
disableAutoConf);
|
||||||
this.signatureAsBase64 = signatureAsBase64;
|
}
|
||||||
this.ownerPubKeyBytes = ownerPubKeyBytes;
|
|
||||||
this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
|
|
||||||
|
|
||||||
ownerPubKey = Sig.getPublicKeyFromBytes(ownerPubKeyBytes);
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// PROTO BUFFER
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public Filter(List<String> bannedOfferIds,
|
||||||
|
List<String> bannedNodeAddress,
|
||||||
|
List<PaymentAccountFilter> bannedPaymentAccounts,
|
||||||
|
List<String> bannedCurrencies,
|
||||||
|
List<String> bannedPaymentMethods,
|
||||||
|
List<String> arbitrators,
|
||||||
|
List<String> seedNodes,
|
||||||
|
List<String> priceRelayNodes,
|
||||||
|
boolean preventPublicBtcNetwork,
|
||||||
|
List<String> btcNodes,
|
||||||
|
boolean disableDao,
|
||||||
|
String disableDaoBelowVersion,
|
||||||
|
String disableTradeBelowVersion,
|
||||||
|
List<String> mediators,
|
||||||
|
List<String> refundAgents,
|
||||||
|
List<String> bannedAccountWitnessSignerPubKeys,
|
||||||
|
List<String> btcFeeReceiverAddresses,
|
||||||
|
byte[] ownerPubKeyBytes,
|
||||||
|
long creationDate,
|
||||||
|
@Nullable Map<String, String> extraDataMap,
|
||||||
|
@Nullable String signatureAsBase64,
|
||||||
|
String signerPubKeyAsHex,
|
||||||
|
List<String> bannedPrivilegedDevPubKeys,
|
||||||
|
boolean disableAutoConf) {
|
||||||
|
this.bannedOfferIds = bannedOfferIds;
|
||||||
|
this.bannedNodeAddress = bannedNodeAddress;
|
||||||
|
this.bannedPaymentAccounts = bannedPaymentAccounts;
|
||||||
|
this.bannedCurrencies = bannedCurrencies;
|
||||||
|
this.bannedPaymentMethods = bannedPaymentMethods;
|
||||||
|
this.arbitrators = arbitrators;
|
||||||
|
this.seedNodes = seedNodes;
|
||||||
|
this.priceRelayNodes = priceRelayNodes;
|
||||||
|
this.preventPublicBtcNetwork = preventPublicBtcNetwork;
|
||||||
|
this.btcNodes = btcNodes;
|
||||||
|
this.disableDao = disableDao;
|
||||||
|
this.disableDaoBelowVersion = disableDaoBelowVersion;
|
||||||
|
this.disableTradeBelowVersion = disableTradeBelowVersion;
|
||||||
|
this.mediators = mediators;
|
||||||
|
this.refundAgents = refundAgents;
|
||||||
|
this.bannedAccountWitnessSignerPubKeys = bannedAccountWitnessSignerPubKeys;
|
||||||
|
this.btcFeeReceiverAddresses = btcFeeReceiverAddresses;
|
||||||
|
this.disableAutoConf = disableAutoConf;
|
||||||
|
this.ownerPubKeyBytes = ownerPubKeyBytes;
|
||||||
|
this.creationDate = creationDate;
|
||||||
|
this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
|
||||||
|
this.signatureAsBase64 = signatureAsBase64;
|
||||||
|
this.signerPubKeyAsHex = signerPubKeyAsHex;
|
||||||
|
this.bannedPrivilegedDevPubKeys = bannedPrivilegedDevPubKeys;
|
||||||
|
|
||||||
|
// ownerPubKeyBytes can be null when called from tests
|
||||||
|
if (ownerPubKeyBytes != null) {
|
||||||
|
ownerPubKey = Sig.getPublicKeyFromBytes(ownerPubKeyBytes);
|
||||||
|
} else {
|
||||||
|
ownerPubKey = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public protobuf.StoragePayload toProtoMessage() {
|
public protobuf.StoragePayload toProtoMessage() {
|
||||||
checkNotNull(signatureAsBase64, "signatureAsBase64 must not be null");
|
|
||||||
checkNotNull(ownerPubKeyBytes, "ownerPubKeyBytes must not be null");
|
|
||||||
List<protobuf.PaymentAccountFilter> paymentAccountFilterList = bannedPaymentAccounts.stream()
|
List<protobuf.PaymentAccountFilter> paymentAccountFilterList = bannedPaymentAccounts.stream()
|
||||||
.map(PaymentAccountFilter::toProtoMessage)
|
.map(PaymentAccountFilter::toProtoMessage)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
final protobuf.Filter.Builder builder = protobuf.Filter.newBuilder()
|
|
||||||
.addAllBannedOfferIds(bannedOfferIds)
|
protobuf.Filter.Builder builder = protobuf.Filter.newBuilder().addAllBannedOfferIds(bannedOfferIds)
|
||||||
.addAllBannedNodeAddress(bannedNodeAddress)
|
.addAllBannedNodeAddress(bannedNodeAddress)
|
||||||
.addAllBannedPaymentAccounts(paymentAccountFilterList)
|
.addAllBannedPaymentAccounts(paymentAccountFilterList)
|
||||||
.setSignatureAsBase64(signatureAsBase64)
|
.addAllBannedCurrencies(bannedCurrencies)
|
||||||
.setOwnerPubKeyBytes(ByteString.copyFrom(ownerPubKeyBytes))
|
.addAllBannedPaymentMethods(bannedPaymentMethods)
|
||||||
|
.addAllArbitrators(arbitrators)
|
||||||
|
.addAllSeedNodes(seedNodes)
|
||||||
|
.addAllPriceRelayNodes(priceRelayNodes)
|
||||||
.setPreventPublicBtcNetwork(preventPublicBtcNetwork)
|
.setPreventPublicBtcNetwork(preventPublicBtcNetwork)
|
||||||
|
.addAllBtcNodes(btcNodes)
|
||||||
.setDisableDao(disableDao)
|
.setDisableDao(disableDao)
|
||||||
|
.setDisableDaoBelowVersion(disableDaoBelowVersion)
|
||||||
|
.setDisableTradeBelowVersion(disableTradeBelowVersion)
|
||||||
|
.addAllMediators(mediators)
|
||||||
|
.addAllRefundAgents(refundAgents)
|
||||||
|
.addAllBannedSignerPubKeys(bannedAccountWitnessSignerPubKeys)
|
||||||
|
.addAllBtcFeeReceiverAddresses(btcFeeReceiverAddresses)
|
||||||
|
.setOwnerPubKeyBytes(ByteString.copyFrom(ownerPubKeyBytes))
|
||||||
|
.setSignerPubKeyAsHex(signerPubKeyAsHex)
|
||||||
|
.setCreationDate(creationDate)
|
||||||
|
.addAllBannedPrivilegedDevPubKeys(bannedPrivilegedDevPubKeys)
|
||||||
.setDisableAutoConf(disableAutoConf);
|
.setDisableAutoConf(disableAutoConf);
|
||||||
|
|
||||||
Optional.ofNullable(bannedCurrencies).ifPresent(builder::addAllBannedCurrencies);
|
Optional.ofNullable(signatureAsBase64).ifPresent(builder::setSignatureAsBase64);
|
||||||
Optional.ofNullable(bannedPaymentMethods).ifPresent(builder::addAllBannedPaymentMethods);
|
|
||||||
Optional.ofNullable(arbitrators).ifPresent(builder::addAllArbitrators);
|
|
||||||
Optional.ofNullable(seedNodes).ifPresent(builder::addAllSeedNodes);
|
|
||||||
Optional.ofNullable(priceRelayNodes).ifPresent(builder::addAllPriceRelayNodes);
|
|
||||||
Optional.ofNullable(btcNodes).ifPresent(builder::addAllBtcNodes);
|
|
||||||
Optional.ofNullable(disableDaoBelowVersion).ifPresent(builder::setDisableDaoBelowVersion);
|
|
||||||
Optional.ofNullable(disableTradeBelowVersion).ifPresent(builder::setDisableTradeBelowVersion);
|
|
||||||
Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
|
Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
|
||||||
Optional.ofNullable(mediators).ifPresent(builder::addAllMediators);
|
|
||||||
Optional.ofNullable(refundAgents).ifPresent(builder::addAllRefundAgents);
|
|
||||||
Optional.ofNullable(bannedSignerPubKeys).ifPresent(builder::addAllBannedSignerPubKeys);
|
|
||||||
Optional.ofNullable(btcFeeReceiverAddresses).ifPresent(builder::addAllBtcFeeReceiverAddresses);
|
|
||||||
|
|
||||||
return protobuf.StoragePayload.newBuilder().setFilter(builder).build();
|
return protobuf.StoragePayload.newBuilder().setFilter(builder).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Filter fromProto(protobuf.Filter proto) {
|
public static Filter fromProto(protobuf.Filter proto) {
|
||||||
return new Filter(new ArrayList<>(proto.getBannedOfferIdsList()),
|
List<PaymentAccountFilter> bannedPaymentAccountsList = proto.getBannedPaymentAccountsList().stream()
|
||||||
new ArrayList<>(proto.getBannedNodeAddressList()),
|
.map(PaymentAccountFilter::fromProto)
|
||||||
proto.getBannedPaymentAccountsList().stream()
|
.collect(Collectors.toList());
|
||||||
.map(PaymentAccountFilter::fromProto)
|
|
||||||
.collect(Collectors.toList()),
|
|
||||||
CollectionUtils.isEmpty(proto.getBannedCurrenciesList()) ? null : new ArrayList<>(proto.getBannedCurrenciesList()),
|
return new Filter(ProtoUtil.protocolStringListToList(proto.getBannedOfferIdsList()),
|
||||||
CollectionUtils.isEmpty(proto.getBannedPaymentMethodsList()) ? null : new ArrayList<>(proto.getBannedPaymentMethodsList()),
|
ProtoUtil.protocolStringListToList(proto.getBannedNodeAddressList()),
|
||||||
CollectionUtils.isEmpty(proto.getArbitratorsList()) ? null : new ArrayList<>(proto.getArbitratorsList()),
|
bannedPaymentAccountsList,
|
||||||
CollectionUtils.isEmpty(proto.getSeedNodesList()) ? null : new ArrayList<>(proto.getSeedNodesList()),
|
ProtoUtil.protocolStringListToList(proto.getBannedCurrenciesList()),
|
||||||
CollectionUtils.isEmpty(proto.getPriceRelayNodesList()) ? null : new ArrayList<>(proto.getPriceRelayNodesList()),
|
ProtoUtil.protocolStringListToList(proto.getBannedPaymentMethodsList()),
|
||||||
|
ProtoUtil.protocolStringListToList(proto.getArbitratorsList()),
|
||||||
|
ProtoUtil.protocolStringListToList(proto.getSeedNodesList()),
|
||||||
|
ProtoUtil.protocolStringListToList(proto.getPriceRelayNodesList()),
|
||||||
proto.getPreventPublicBtcNetwork(),
|
proto.getPreventPublicBtcNetwork(),
|
||||||
CollectionUtils.isEmpty(proto.getBtcNodesList()) ? null : new ArrayList<>(proto.getBtcNodesList()),
|
ProtoUtil.protocolStringListToList(proto.getBtcNodesList()),
|
||||||
proto.getDisableDao(),
|
proto.getDisableDao(),
|
||||||
proto.getDisableDaoBelowVersion().isEmpty() ? null : proto.getDisableDaoBelowVersion(),
|
proto.getDisableDaoBelowVersion(),
|
||||||
proto.getDisableTradeBelowVersion().isEmpty() ? null : proto.getDisableTradeBelowVersion(),
|
proto.getDisableTradeBelowVersion(),
|
||||||
proto.getSignatureAsBase64(),
|
ProtoUtil.protocolStringListToList(proto.getMediatorsList()),
|
||||||
|
ProtoUtil.protocolStringListToList(proto.getRefundAgentsList()),
|
||||||
|
ProtoUtil.protocolStringListToList(proto.getBannedSignerPubKeysList()),
|
||||||
|
ProtoUtil.protocolStringListToList(proto.getBtcFeeReceiverAddressesList()),
|
||||||
proto.getOwnerPubKeyBytes().toByteArray(),
|
proto.getOwnerPubKeyBytes().toByteArray(),
|
||||||
|
proto.getCreationDate(),
|
||||||
CollectionUtils.isEmpty(proto.getExtraDataMap()) ? null : proto.getExtraDataMap(),
|
CollectionUtils.isEmpty(proto.getExtraDataMap()) ? null : proto.getExtraDataMap(),
|
||||||
CollectionUtils.isEmpty(proto.getMediatorsList()) ? null : new ArrayList<>(proto.getMediatorsList()),
|
proto.getSignatureAsBase64(),
|
||||||
CollectionUtils.isEmpty(proto.getRefundAgentsList()) ? null : new ArrayList<>(proto.getRefundAgentsList()),
|
proto.getSignerPubKeyAsHex(),
|
||||||
CollectionUtils.isEmpty(proto.getBannedSignerPubKeysList()) ?
|
ProtoUtil.protocolStringListToList(proto.getBannedPrivilegedDevPubKeysList()),
|
||||||
null : new ArrayList<>(proto.getBannedSignerPubKeysList()),
|
proto.getDisableAutoConf()
|
||||||
CollectionUtils.isEmpty(proto.getBtcFeeReceiverAddressesList()) ? null :
|
);
|
||||||
new ArrayList<>(proto.getBtcFeeReceiverAddressesList()),
|
|
||||||
proto.getDisableAutoConf());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -274,13 +334,6 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
|||||||
return TimeUnit.DAYS.toMillis(180);
|
return TimeUnit.DAYS.toMillis(180);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSigAndPubKey(String signatureAsBase64, PublicKey ownerPubKey) {
|
|
||||||
this.signatureAsBase64 = signatureAsBase64;
|
|
||||||
this.ownerPubKey = ownerPubKey;
|
|
||||||
|
|
||||||
ownerPubKeyBytes = Sig.getPublicKeyBytes(this.ownerPubKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Filter{" +
|
return "Filter{" +
|
||||||
@ -294,15 +347,20 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
|
|||||||
",\n priceRelayNodes=" + priceRelayNodes +
|
",\n priceRelayNodes=" + priceRelayNodes +
|
||||||
",\n preventPublicBtcNetwork=" + preventPublicBtcNetwork +
|
",\n preventPublicBtcNetwork=" + preventPublicBtcNetwork +
|
||||||
",\n btcNodes=" + btcNodes +
|
",\n btcNodes=" + btcNodes +
|
||||||
",\n extraDataMap=" + extraDataMap +
|
",\n signatureAsBase64='" + signatureAsBase64 + '\'' +
|
||||||
|
",\n signerPubKeyAsHex='" + signerPubKeyAsHex + '\'' +
|
||||||
|
",\n ownerPubKeyBytes=" + Utilities.bytesAsHexString(ownerPubKeyBytes) +
|
||||||
",\n disableDao=" + disableDao +
|
",\n disableDao=" + disableDao +
|
||||||
",\n disableDaoBelowVersion='" + disableDaoBelowVersion + '\'' +
|
",\n disableDaoBelowVersion='" + disableDaoBelowVersion + '\'' +
|
||||||
",\n disableTradeBelowVersion='" + disableTradeBelowVersion + '\'' +
|
",\n disableTradeBelowVersion='" + disableTradeBelowVersion + '\'' +
|
||||||
",\n mediators=" + mediators +
|
",\n mediators=" + mediators +
|
||||||
",\n refundAgents=" + refundAgents +
|
",\n refundAgents=" + refundAgents +
|
||||||
",\n bannedSignerPubKeys=" + bannedSignerPubKeys +
|
",\n bannedAccountWitnessSignerPubKeys=" + bannedAccountWitnessSignerPubKeys +
|
||||||
|
",\n bannedPrivilegedDevPubKeys=" + bannedPrivilegedDevPubKeys +
|
||||||
",\n btcFeeReceiverAddresses=" + btcFeeReceiverAddresses +
|
",\n btcFeeReceiverAddresses=" + btcFeeReceiverAddresses +
|
||||||
",\n disableAutoConf=" + disableAutoConf +
|
",\n disableAutoConf=" + disableAutoConf +
|
||||||
|
",\n creationDate=" + creationDate +
|
||||||
|
",\n extraDataMap=" + extraDataMap +
|
||||||
"\n}";
|
"\n}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,9 +29,7 @@ import bisq.network.p2p.P2PService;
|
|||||||
import bisq.network.p2p.P2PServiceListener;
|
import bisq.network.p2p.P2PServiceListener;
|
||||||
import bisq.network.p2p.storage.HashMapChangedListener;
|
import bisq.network.p2p.storage.HashMapChangedListener;
|
||||||
import bisq.network.p2p.storage.payload.ProtectedStorageEntry;
|
import bisq.network.p2p.storage.payload.ProtectedStorageEntry;
|
||||||
import bisq.network.p2p.storage.payload.ProtectedStoragePayload;
|
|
||||||
|
|
||||||
import bisq.common.UserThread;
|
|
||||||
import bisq.common.app.DevEnv;
|
import bisq.common.app.DevEnv;
|
||||||
import bisq.common.app.Version;
|
import bisq.common.app.Version;
|
||||||
import bisq.common.config.Config;
|
import bisq.common.config.Config;
|
||||||
@ -39,7 +37,7 @@ import bisq.common.config.ConfigFileEditor;
|
|||||||
import bisq.common.crypto.KeyRing;
|
import bisq.common.crypto.KeyRing;
|
||||||
|
|
||||||
import org.bitcoinj.core.ECKey;
|
import org.bitcoinj.core.ECKey;
|
||||||
import org.bitcoinj.core.Utils;
|
import org.bitcoinj.core.Sha256Hash;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
@ -47,33 +45,36 @@ import javax.inject.Named;
|
|||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
|
||||||
import java.security.SignatureException;
|
import org.spongycastle.util.encoders.Base64;
|
||||||
|
|
||||||
|
import java.security.PublicKey;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static org.bitcoinj.core.Utils.HEX;
|
import static org.bitcoinj.core.Utils.HEX;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We only support one active filter, if we receive multiple we use the one with the more recent creationDate.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
public class FilterManager {
|
public class FilterManager {
|
||||||
|
private static final String BANNED_PRICE_RELAY_NODES = "bannedPriceRelayNodes";
|
||||||
private static final Logger log = LoggerFactory.getLogger(FilterManager.class);
|
private static final String BANNED_SEED_NODES = "bannedSeedNodes";
|
||||||
|
private static final String BANNED_BTC_NODES = "bannedBtcNodes";
|
||||||
public static final String BANNED_PRICE_RELAY_NODES = "bannedPriceRelayNodes";
|
|
||||||
public static final String BANNED_SEED_NODES = "bannedSeedNodes";
|
|
||||||
public static final String BANNED_BTC_NODES = "bannedBtcNodes";
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -90,16 +91,15 @@ public class FilterManager {
|
|||||||
private final Preferences preferences;
|
private final Preferences preferences;
|
||||||
private final ConfigFileEditor configFileEditor;
|
private final ConfigFileEditor configFileEditor;
|
||||||
private final ProvidersRepository providersRepository;
|
private final ProvidersRepository providersRepository;
|
||||||
private boolean ignoreDevMsg;
|
private final boolean ignoreDevMsg;
|
||||||
private final ObjectProperty<Filter> filterProperty = new SimpleObjectProperty<>();
|
private final ObjectProperty<Filter> filterProperty = new SimpleObjectProperty<>();
|
||||||
private final List<Listener> listeners = new CopyOnWriteArrayList<>();
|
private final List<Listener> listeners = new CopyOnWriteArrayList<>();
|
||||||
|
private final List<String> publicKeys;
|
||||||
private final String pubKeyAsHex;
|
|
||||||
private ECKey filterSigningKey;
|
private ECKey filterSigningKey;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Constructor, Initialization
|
// Constructor
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@ -118,48 +118,53 @@ public class FilterManager {
|
|||||||
this.configFileEditor = new ConfigFileEditor(config.configFile);
|
this.configFileEditor = new ConfigFileEditor(config.configFile);
|
||||||
this.providersRepository = providersRepository;
|
this.providersRepository = providersRepository;
|
||||||
this.ignoreDevMsg = ignoreDevMsg;
|
this.ignoreDevMsg = ignoreDevMsg;
|
||||||
pubKeyAsHex = useDevPrivilegeKeys ?
|
|
||||||
DevEnv.DEV_PRIVILEGE_PUB_KEY :
|
publicKeys = useDevPrivilegeKeys ?
|
||||||
"022ac7b7766b0aedff82962522c2c14fb8d1961dabef6e5cfd10edc679456a32f1";
|
Collections.singletonList(DevEnv.DEV_PRIVILEGE_PUB_KEY) :
|
||||||
|
List.of("0358d47858acdc41910325fce266571540681ef83a0d6fedce312bef9810793a27",
|
||||||
|
"029340c3e7d4bb0f9e651b5f590b434fecb6175aeaa57145c7804ff05d210e534f",
|
||||||
|
"034dc7530bf66ffd9580aa98031ea9a18ac2d269f7c56c0e71eca06105b9ed69f9");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// API
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public void onAllServicesInitialized() {
|
public void onAllServicesInitialized() {
|
||||||
if (!ignoreDevMsg) {
|
if (ignoreDevMsg) {
|
||||||
|
return;
|
||||||
final List<ProtectedStorageEntry> list = new ArrayList<>(p2PService.getP2PDataStorage().getMap().values());
|
|
||||||
list.forEach(e -> {
|
|
||||||
final ProtectedStoragePayload protectedStoragePayload = e.getProtectedStoragePayload();
|
|
||||||
if (protectedStoragePayload instanceof Filter)
|
|
||||||
addFilter((Filter) protectedStoragePayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
p2PService.addHashSetChangedListener(new HashMapChangedListener() {
|
|
||||||
@Override
|
|
||||||
public void onAdded(Collection<ProtectedStorageEntry> protectedStorageEntries) {
|
|
||||||
protectedStorageEntries.forEach(protectedStorageEntry -> {
|
|
||||||
if (protectedStorageEntry.getProtectedStoragePayload() instanceof Filter) {
|
|
||||||
Filter filter = (Filter) protectedStorageEntry.getProtectedStoragePayload();
|
|
||||||
boolean wasValid = addFilter(filter);
|
|
||||||
if (!wasValid) {
|
|
||||||
UserThread.runAfter(() -> p2PService.getP2PDataStorage().removeInvalidProtectedStorageEntry(protectedStorageEntry), 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRemoved(Collection<ProtectedStorageEntry> protectedStorageEntries) {
|
|
||||||
protectedStorageEntries.forEach(protectedStorageEntry -> {
|
|
||||||
if (protectedStorageEntry.getProtectedStoragePayload() instanceof Filter) {
|
|
||||||
Filter filter = (Filter) protectedStorageEntry.getProtectedStoragePayload();
|
|
||||||
if (verifySignature(filter) && getFilter().equals(filter))
|
|
||||||
resetFilters();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p2PService.getP2PDataStorage().getMap().values().stream()
|
||||||
|
.map(ProtectedStorageEntry::getProtectedStoragePayload)
|
||||||
|
.filter(protectedStoragePayload -> protectedStoragePayload instanceof Filter)
|
||||||
|
.map(protectedStoragePayload -> (Filter) protectedStoragePayload)
|
||||||
|
.forEach(this::onFilterAddedFromNetwork);
|
||||||
|
|
||||||
|
p2PService.addHashSetChangedListener(new HashMapChangedListener() {
|
||||||
|
@Override
|
||||||
|
public void onAdded(Collection<ProtectedStorageEntry> protectedStorageEntries) {
|
||||||
|
protectedStorageEntries.stream()
|
||||||
|
.filter(protectedStorageEntry -> protectedStorageEntry.getProtectedStoragePayload() instanceof Filter)
|
||||||
|
.forEach(protectedStorageEntry -> {
|
||||||
|
Filter filter = (Filter) protectedStorageEntry.getProtectedStoragePayload();
|
||||||
|
onFilterAddedFromNetwork(filter);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRemoved(Collection<ProtectedStorageEntry> protectedStorageEntries) {
|
||||||
|
protectedStorageEntries.stream()
|
||||||
|
.filter(protectedStorageEntry -> protectedStorageEntry.getProtectedStoragePayload() instanceof Filter)
|
||||||
|
.forEach(protectedStorageEntry -> {
|
||||||
|
Filter filter = (Filter) protectedStorageEntry.getProtectedStoragePayload();
|
||||||
|
onFilterRemovedFromNetwork(filter);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
p2PService.addP2PServiceListener(new P2PServiceListener() {
|
p2PService.addP2PServiceListener(new P2PServiceListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onDataReceived() {
|
public void onDataReceived() {
|
||||||
@ -175,12 +180,12 @@ public class FilterManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUpdatedDataReceived() {
|
public void onUpdatedDataReceived() {
|
||||||
// We should have received all data at that point and if the filers were not set we
|
// We should have received all data at that point and if the filters were not set we
|
||||||
// clean up as it might be that we missed the filter remove message if we have not been online.
|
// clean up the persisted banned nodes in the options file as it might be that we missed the filter
|
||||||
UserThread.runAfter(() -> {
|
// remove message if we have not been online.
|
||||||
if (filterProperty.get() == null)
|
if (filterProperty.get() == null) {
|
||||||
resetFilters();
|
clearBannedNodes();
|
||||||
}, 1);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -201,54 +206,94 @@ public class FilterManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetFilters() {
|
public boolean isPrivilegedDevPubKeyBanned(String pubKeyAsHex) {
|
||||||
saveBannedNodes(BANNED_BTC_NODES, null);
|
Filter filter = getFilter();
|
||||||
saveBannedNodes(BANNED_SEED_NODES, null);
|
if (filter == null) {
|
||||||
saveBannedNodes(BANNED_PRICE_RELAY_NODES, null);
|
|
||||||
|
|
||||||
if (providersRepository.getBannedNodes() != null)
|
|
||||||
providersRepository.applyBannedNodes(null);
|
|
||||||
|
|
||||||
filterProperty.set(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean addFilter(Filter filter) {
|
|
||||||
if (verifySignature(filter)) {
|
|
||||||
// Seed nodes are requested at startup before we get the filter so we only apply the banned
|
|
||||||
// nodes at the next startup and don't update the list in the P2P network domain.
|
|
||||||
// We persist it to the property file which is read before any other initialisation.
|
|
||||||
saveBannedNodes(BANNED_SEED_NODES, filter.getSeedNodes());
|
|
||||||
saveBannedNodes(BANNED_BTC_NODES, filter.getBtcNodes());
|
|
||||||
|
|
||||||
// Banned price relay nodes we can apply at runtime
|
|
||||||
final List<String> priceRelayNodes = filter.getPriceRelayNodes();
|
|
||||||
saveBannedNodes(BANNED_PRICE_RELAY_NODES, priceRelayNodes);
|
|
||||||
|
|
||||||
providersRepository.applyBannedNodes(priceRelayNodes);
|
|
||||||
|
|
||||||
filterProperty.set(filter);
|
|
||||||
listeners.forEach(e -> e.onFilterAdded(filter));
|
|
||||||
|
|
||||||
if (filter.isPreventPublicBtcNetwork() &&
|
|
||||||
preferences.getBitcoinNodesOptionOrdinal() == BtcNodes.BitcoinNodesOption.PUBLIC.ordinal())
|
|
||||||
preferences.setBitcoinNodesOptionOrdinal(BtcNodes.BitcoinNodesOption.PROVIDED.ordinal());
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return filter.getBannedPrivilegedDevPubKeys().contains(pubKeyAsHex);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveBannedNodes(String optionName, List<String> bannedNodes) {
|
public boolean canAddDevFilter(String privKeyString) {
|
||||||
if (bannedNodes != null)
|
if (privKeyString == null || privKeyString.isEmpty()) {
|
||||||
configFileEditor.setOption(optionName, String.join(",", bannedNodes));
|
return false;
|
||||||
else
|
}
|
||||||
configFileEditor.clearOption(optionName);
|
if (!isValidDevPrivilegeKey(privKeyString)) {
|
||||||
|
log.warn("Key in invalid");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ECKey ecKeyFromPrivate = toECKey(privKeyString);
|
||||||
|
String pubKeyAsHex = getPubKeyAsHex(ecKeyFromPrivate);
|
||||||
|
if (isPrivilegedDevPubKeyBanned(pubKeyAsHex)) {
|
||||||
|
log.warn("Pub key is banned.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSignerPubKeyAsHex(String privKeyString) {
|
||||||
|
ECKey ecKey = toECKey(privKeyString);
|
||||||
|
return getPubKeyAsHex(ecKey);
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
public void addDevFilter(Filter filterWithoutSig, String privKeyString) {
|
||||||
// API
|
setFilterSigningKey(privKeyString);
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
String signatureAsBase64 = getSignature(filterWithoutSig);
|
||||||
|
Filter filterWithSig = Filter.cloneWithSig(filterWithoutSig, signatureAsBase64);
|
||||||
|
user.setDevelopersFilter(filterWithSig);
|
||||||
|
|
||||||
|
p2PService.addProtectedStorageEntry(filterWithSig);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canRemoveDevFilter(String privKeyString) {
|
||||||
|
if (privKeyString == null || privKeyString.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Filter developersFilter = getDevFilter();
|
||||||
|
if (developersFilter == null) {
|
||||||
|
log.warn("There is no persisted dev filter to be removed.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isValidDevPrivilegeKey(privKeyString)) {
|
||||||
|
log.warn("Key in invalid.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ECKey ecKeyFromPrivate = toECKey(privKeyString);
|
||||||
|
String pubKeyAsHex = getPubKeyAsHex(ecKeyFromPrivate);
|
||||||
|
if (!developersFilter.getSignerPubKeyAsHex().equals(pubKeyAsHex)) {
|
||||||
|
log.warn("pubKeyAsHex derived from private key does not match filterSignerPubKey. " +
|
||||||
|
"filterSignerPubKey={}, pubKeyAsHex derived from private key={}",
|
||||||
|
developersFilter.getSignerPubKeyAsHex(), pubKeyAsHex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPrivilegedDevPubKeyBanned(pubKeyAsHex)) {
|
||||||
|
log.warn("Pub key is banned.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeDevFilter(String privKeyString) {
|
||||||
|
setFilterSigningKey(privKeyString);
|
||||||
|
Filter filterWithSig = user.getDevelopersFilter();
|
||||||
|
if (filterWithSig == null) {
|
||||||
|
// Should not happen as UI button is deactivated in that case
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p2PService.removeData(filterWithSig)) {
|
||||||
|
user.setDevelopersFilter(null);
|
||||||
|
} else {
|
||||||
|
log.warn("Removing dev filter from network failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void addListener(Listener listener) {
|
public void addListener(Listener listener) {
|
||||||
listeners.add(listener);
|
listeners.add(listener);
|
||||||
@ -263,85 +308,15 @@ public class FilterManager {
|
|||||||
return filterProperty.get();
|
return filterProperty.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean addFilterMessageIfKeyIsValid(Filter filter, String privKeyString) {
|
|
||||||
// if there is a previous message we remove that first
|
|
||||||
if (user.getDevelopersFilter() != null)
|
|
||||||
removeFilterMessageIfKeyIsValid(privKeyString);
|
|
||||||
|
|
||||||
boolean isKeyValid = isKeyValid(privKeyString);
|
|
||||||
if (isKeyValid) {
|
|
||||||
signAndAddSignatureToFilter(filter);
|
|
||||||
user.setDevelopersFilter(filter);
|
|
||||||
|
|
||||||
boolean result = p2PService.addProtectedStorageEntry(filter);
|
|
||||||
if (result)
|
|
||||||
log.trace("Add filter to network was successful. FilterMessage = {}", filter);
|
|
||||||
|
|
||||||
}
|
|
||||||
return isKeyValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean removeFilterMessageIfKeyIsValid(String privKeyString) {
|
|
||||||
if (isKeyValid(privKeyString)) {
|
|
||||||
Filter filter = user.getDevelopersFilter();
|
|
||||||
if (filter == null) {
|
|
||||||
log.warn("Developers filter is null");
|
|
||||||
} else if (p2PService.removeData(filter)) {
|
|
||||||
log.trace("Remove filter from network was successful. FilterMessage = {}", filter);
|
|
||||||
user.setDevelopersFilter(null);
|
|
||||||
} else {
|
|
||||||
log.warn("Filter remove failed");
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isKeyValid(String privKeyString) {
|
|
||||||
try {
|
|
||||||
filterSigningKey = ECKey.fromPrivate(new BigInteger(1, HEX.decode(privKeyString)));
|
|
||||||
return pubKeyAsHex.equals(Utils.HEX.encode(filterSigningKey.getPubKey()));
|
|
||||||
} catch (Throwable t) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void signAndAddSignatureToFilter(Filter filter) {
|
|
||||||
filter.setSigAndPubKey(filterSigningKey.signMessage(getHexFromData(filter)), keyRing.getSignatureKeyPair().getPublic());
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean verifySignature(Filter filter) {
|
|
||||||
try {
|
|
||||||
ECKey.fromPublicOnly(HEX.decode(pubKeyAsHex)).verifyMessage(getHexFromData(filter), filter.getSignatureAsBase64());
|
|
||||||
return true;
|
|
||||||
} catch (SignatureException e) {
|
|
||||||
log.warn("verifySignature failed. filter={}", filter);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't use full data from Filter as we are only interested in the filter data not the sig and keys
|
|
||||||
private String getHexFromData(Filter filter) {
|
|
||||||
protobuf.Filter.Builder builder = protobuf.Filter.newBuilder()
|
|
||||||
.addAllBannedOfferIds(filter.getBannedOfferIds())
|
|
||||||
.addAllBannedNodeAddress(filter.getBannedNodeAddress())
|
|
||||||
.addAllBannedPaymentAccounts(filter.getBannedPaymentAccounts().stream()
|
|
||||||
.map(PaymentAccountFilter::toProtoMessage)
|
|
||||||
.collect(Collectors.toList()));
|
|
||||||
|
|
||||||
Optional.ofNullable(filter.getBannedCurrencies()).ifPresent(builder::addAllBannedCurrencies);
|
|
||||||
Optional.ofNullable(filter.getBannedPaymentMethods()).ifPresent(builder::addAllBannedPaymentMethods);
|
|
||||||
Optional.ofNullable(filter.getBannedSignerPubKeys()).ifPresent(builder::addAllBannedSignerPubKeys);
|
|
||||||
|
|
||||||
return Utils.HEX.encode(builder.build().toByteArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public Filter getDevelopersFilter() {
|
public Filter getDevFilter() {
|
||||||
return user.getDevelopersFilter();
|
return user.getDevelopersFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PublicKey getOwnerPubKey() {
|
||||||
|
return keyRing.getSignatureKeyPair().getPublic();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isCurrencyBanned(String currencyCode) {
|
public boolean isCurrencyBanned(String currencyCode) {
|
||||||
return getFilter() != null &&
|
return getFilter() != null &&
|
||||||
getFilter().getBannedCurrencies() != null &&
|
getFilter().getBannedCurrencies() != null &&
|
||||||
@ -396,8 +371,8 @@ public class FilterManager {
|
|||||||
return requireUpdateToNewVersion;
|
return requireUpdateToNewVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPeersPaymentAccountDataAreBanned(PaymentAccountPayload paymentAccountPayload,
|
public boolean arePeersPaymentAccountDataBanned(PaymentAccountPayload paymentAccountPayload,
|
||||||
PaymentAccountFilter[] appliedPaymentAccountFilter) {
|
PaymentAccountFilter[] appliedPaymentAccountFilter) {
|
||||||
return getFilter() != null &&
|
return getFilter() != null &&
|
||||||
getFilter().getBannedPaymentAccounts().stream()
|
getFilter().getBannedPaymentAccounts().stream()
|
||||||
.anyMatch(paymentAccountFilter -> {
|
.anyMatch(paymentAccountFilter -> {
|
||||||
@ -419,11 +394,183 @@ public class FilterManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSignerPubKeyBanned(String signerPubKeyAsHex) {
|
public boolean isWitnessSignerPubKeyBanned(String witnessSignerPubKeyAsHex) {
|
||||||
return getFilter() != null &&
|
return getFilter() != null &&
|
||||||
getFilter().getBannedSignerPubKeys() != null &&
|
getFilter().getBannedAccountWitnessSignerPubKeys() != null &&
|
||||||
getFilter().getBannedSignerPubKeys().stream()
|
getFilter().getBannedAccountWitnessSignerPubKeys().stream()
|
||||||
.anyMatch(e -> e.equals(signerPubKeyAsHex));
|
.anyMatch(e -> e.equals(witnessSignerPubKeyAsHex));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Private
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private void onFilterAddedFromNetwork(Filter newFilter) {
|
||||||
|
Filter currentFilter = getFilter();
|
||||||
|
|
||||||
|
if (!isFilterPublicKeyInList(newFilter)) {
|
||||||
|
log.warn("isFilterPublicKeyInList failed. Filter={}", newFilter);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isSignatureValid(newFilter)) {
|
||||||
|
log.warn("verifySignature failed. Filter={}", newFilter);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentFilter != null) {
|
||||||
|
if (currentFilter.getCreationDate() > newFilter.getCreationDate()) {
|
||||||
|
log.warn("We received a new filter from the network but the creation date is older than the " +
|
||||||
|
"filter we have already. We ignore the new filter.\n" +
|
||||||
|
"New filer={}\n" +
|
||||||
|
"Old filter={}",
|
||||||
|
newFilter, filterProperty.get());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPrivilegedDevPubKeyBanned(newFilter.getSignerPubKeyAsHex())) {
|
||||||
|
log.warn("Pub key of filter is banned. currentFilter={}, newFilter={}", currentFilter, newFilter);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Our new filter is newer so we apply it.
|
||||||
|
// We do not require strict guarantees here (e.g. clocks not synced) as only trusted developers have the key
|
||||||
|
// for deploying filters and this is only in place to avoid unintended situations of multiple filters
|
||||||
|
// from multiple devs or if same dev publishes new filter from different app without the persisted devFilter.
|
||||||
|
filterProperty.set(newFilter);
|
||||||
|
|
||||||
|
// Seed nodes are requested at startup before we get the filter so we only apply the banned
|
||||||
|
// nodes at the next startup and don't update the list in the P2P network domain.
|
||||||
|
// We persist it to the property file which is read before any other initialisation.
|
||||||
|
saveBannedNodes(BANNED_SEED_NODES, newFilter.getSeedNodes());
|
||||||
|
saveBannedNodes(BANNED_BTC_NODES, newFilter.getBtcNodes());
|
||||||
|
|
||||||
|
// Banned price relay nodes we can apply at runtime
|
||||||
|
List<String> priceRelayNodes = newFilter.getPriceRelayNodes();
|
||||||
|
saveBannedNodes(BANNED_PRICE_RELAY_NODES, priceRelayNodes);
|
||||||
|
|
||||||
|
//TODO should be moved to client with listening on onFilterAdded
|
||||||
|
providersRepository.applyBannedNodes(priceRelayNodes);
|
||||||
|
|
||||||
|
//TODO should be moved to client with listening on onFilterAdded
|
||||||
|
if (newFilter.isPreventPublicBtcNetwork() &&
|
||||||
|
preferences.getBitcoinNodesOptionOrdinal() == BtcNodes.BitcoinNodesOption.PUBLIC.ordinal()) {
|
||||||
|
preferences.setBitcoinNodesOptionOrdinal(BtcNodes.BitcoinNodesOption.PROVIDED.ordinal());
|
||||||
|
}
|
||||||
|
|
||||||
|
listeners.forEach(e -> e.onFilterAdded(newFilter));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onFilterRemovedFromNetwork(Filter filter) {
|
||||||
|
if (!isFilterPublicKeyInList(filter)) {
|
||||||
|
log.warn("isFilterPublicKeyInList failed. Filter={}", filter);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isSignatureValid(filter)) {
|
||||||
|
log.warn("verifySignature failed. Filter={}", filter);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't check for banned filter as we want to remove a banned filter anyway.
|
||||||
|
|
||||||
|
if (!filterProperty.get().equals(filter)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearBannedNodes();
|
||||||
|
|
||||||
|
if (filter.equals(user.getDevelopersFilter())) {
|
||||||
|
user.setDevelopersFilter(null);
|
||||||
|
}
|
||||||
|
filterProperty.set(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clears options files from banned nodes
|
||||||
|
private void clearBannedNodes() {
|
||||||
|
saveBannedNodes(BANNED_BTC_NODES, null);
|
||||||
|
saveBannedNodes(BANNED_SEED_NODES, null);
|
||||||
|
saveBannedNodes(BANNED_PRICE_RELAY_NODES, null);
|
||||||
|
|
||||||
|
if (providersRepository.getBannedNodes() != null) {
|
||||||
|
providersRepository.applyBannedNodes(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveBannedNodes(String optionName, List<String> bannedNodes) {
|
||||||
|
if (bannedNodes != null)
|
||||||
|
configFileEditor.setOption(optionName, String.join(",", bannedNodes));
|
||||||
|
else
|
||||||
|
configFileEditor.clearOption(optionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isValidDevPrivilegeKey(String privKeyString) {
|
||||||
|
try {
|
||||||
|
ECKey filterSigningKey = toECKey(privKeyString);
|
||||||
|
String pubKeyAsHex = getPubKeyAsHex(filterSigningKey);
|
||||||
|
return isPublicKeyInList(pubKeyAsHex);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFilterSigningKey(String privKeyString) {
|
||||||
|
this.filterSigningKey = toECKey(privKeyString);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getSignature(Filter filterWithoutSig) {
|
||||||
|
Sha256Hash hash = getSha256Hash(filterWithoutSig);
|
||||||
|
ECKey.ECDSASignature ecdsaSignature = filterSigningKey.sign(hash);
|
||||||
|
byte[] encodeToDER = ecdsaSignature.encodeToDER();
|
||||||
|
return new String(Base64.encode(encodeToDER), StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isFilterPublicKeyInList(Filter filter) {
|
||||||
|
String signerPubKeyAsHex = filter.getSignerPubKeyAsHex();
|
||||||
|
if (!isPublicKeyInList(signerPubKeyAsHex)) {
|
||||||
|
log.warn("signerPubKeyAsHex from filter is not part of our pub key list. filter={}, publicKeys={}", filter, publicKeys);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPublicKeyInList(String pubKeyAsHex) {
|
||||||
|
boolean isPublicKeyInList = publicKeys.contains(pubKeyAsHex);
|
||||||
|
if (!isPublicKeyInList) {
|
||||||
|
log.warn("pubKeyAsHex is not part of our pub key list. pubKeyAsHex={}, publicKeys={}", pubKeyAsHex, publicKeys);
|
||||||
|
}
|
||||||
|
return isPublicKeyInList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSignatureValid(Filter filter) {
|
||||||
|
try {
|
||||||
|
Filter filterForSigVerification = Filter.cloneWithoutSig(filter);
|
||||||
|
Sha256Hash hash = getSha256Hash(filterForSigVerification);
|
||||||
|
|
||||||
|
checkNotNull(filter.getSignatureAsBase64(), "filter.getSignatureAsBase64() must not be null");
|
||||||
|
byte[] sigData = Base64.decode(filter.getSignatureAsBase64());
|
||||||
|
ECKey.ECDSASignature ecdsaSignature = ECKey.ECDSASignature.decodeFromDER(sigData);
|
||||||
|
|
||||||
|
String signerPubKeyAsHex = filter.getSignerPubKeyAsHex();
|
||||||
|
byte[] decode = HEX.decode(signerPubKeyAsHex);
|
||||||
|
ECKey ecPubKey = ECKey.fromPublicOnly(decode);
|
||||||
|
return ecPubKey.verify(hash, ecdsaSignature);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
log.warn("verifySignature failed. filter={}", filter);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ECKey toECKey(String privKeyString) {
|
||||||
|
return ECKey.fromPrivate(new BigInteger(1, HEX.decode(privKeyString)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Sha256Hash getSha256Hash(Filter filter) {
|
||||||
|
byte[] filterData = filter.toProtoMessage().toByteArray();
|
||||||
|
return Sha256Hash.of(filterData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPubKeyAsHex(ECKey ecKey) {
|
||||||
|
return HEX.encode(ecKey.getPubKey());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,7 +129,9 @@ public class Price extends MonetaryWrapper implements Comparable<Price> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String toFriendlyString() {
|
public String toFriendlyString() {
|
||||||
return monetary instanceof Altcoin ? ((Altcoin) monetary).toFriendlyString() : ((Fiat) monetary).toFriendlyString();
|
return monetary instanceof Altcoin ?
|
||||||
|
((Altcoin) monetary).toFriendlyString() + "/BTC" :
|
||||||
|
((Fiat) monetary).toFriendlyString().replace(((Fiat) monetary).currencyCode, "") + "BTC/" + ((Fiat) monetary).currencyCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toPlainString() {
|
public String toPlainString() {
|
||||||
|
@ -48,6 +48,9 @@ import javax.annotation.Nullable;
|
|||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
// OfferPayload has about 1.4 kb. We should look into options to make it smaller but will be hard to do it in a
|
||||||
|
// backward compatible way. Maybe a candidate when segwit activation is done as hardfork?
|
||||||
|
|
||||||
@EqualsAndHashCode
|
@EqualsAndHashCode
|
||||||
@Getter
|
@Getter
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@ -36,11 +36,15 @@ public final class RevolutAccount extends PaymentAccount {
|
|||||||
return new RevolutAccountPayload(paymentMethod.getId(), id);
|
return new RevolutAccountPayload(paymentMethod.getId(), id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAccountId(String accountId) {
|
public void setUserName(String userName) {
|
||||||
((RevolutAccountPayload) paymentAccountPayload).setAccountId(accountId);
|
((RevolutAccountPayload) paymentAccountPayload).setUserName(userName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAccountId() {
|
public String getUserName() {
|
||||||
return ((RevolutAccountPayload) paymentAccountPayload).getAccountId();
|
return ((RevolutAccountPayload) paymentAccountPayload).getUserName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean userNameNotSet() {
|
||||||
|
return ((RevolutAccountPayload) paymentAccountPayload).userNameNotSet();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,27 +19,37 @@ package bisq.core.payment.payload;
|
|||||||
|
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
|
|
||||||
|
import bisq.common.proto.ProtoUtil;
|
||||||
|
|
||||||
import com.google.protobuf.Message;
|
import com.google.protobuf.Message;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@ToString
|
@ToString
|
||||||
@Setter
|
|
||||||
@Getter
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public final class RevolutAccountPayload extends PaymentAccountPayload {
|
public final class RevolutAccountPayload extends PaymentAccountPayload {
|
||||||
|
// Not used anymore from outside. Only used as internal Id to not break existing account witness objects
|
||||||
private String accountId = "";
|
private String accountId = "";
|
||||||
|
|
||||||
|
// Was added in 1.3.8
|
||||||
|
// To not break signed accounts we keep accountId as internal id used for signing.
|
||||||
|
// Old accounts get a popup to add the new required field userName but accountId is
|
||||||
|
// left unchanged. Newly created accounts fill accountId with the value of userName.
|
||||||
|
// In the UI we only use userName.
|
||||||
|
@Nullable
|
||||||
|
private String userName = null;
|
||||||
|
|
||||||
public RevolutAccountPayload(String paymentMethod, String id) {
|
public RevolutAccountPayload(String paymentMethod, String id) {
|
||||||
super(paymentMethod, id);
|
super(paymentMethod, id);
|
||||||
}
|
}
|
||||||
@ -52,6 +62,7 @@ public final class RevolutAccountPayload extends PaymentAccountPayload {
|
|||||||
private RevolutAccountPayload(String paymentMethod,
|
private RevolutAccountPayload(String paymentMethod,
|
||||||
String id,
|
String id,
|
||||||
String accountId,
|
String accountId,
|
||||||
|
@Nullable String userName,
|
||||||
long maxTradePeriod,
|
long maxTradePeriod,
|
||||||
Map<String, String> excludeFromJsonDataMap) {
|
Map<String, String> excludeFromJsonDataMap) {
|
||||||
super(paymentMethod,
|
super(paymentMethod,
|
||||||
@ -60,20 +71,24 @@ public final class RevolutAccountPayload extends PaymentAccountPayload {
|
|||||||
excludeFromJsonDataMap);
|
excludeFromJsonDataMap);
|
||||||
|
|
||||||
this.accountId = accountId;
|
this.accountId = accountId;
|
||||||
|
this.userName = userName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Message toProtoMessage() {
|
public Message toProtoMessage() {
|
||||||
return getPaymentAccountPayloadBuilder()
|
protobuf.RevolutAccountPayload.Builder revolutBuilder = protobuf.RevolutAccountPayload.newBuilder()
|
||||||
.setRevolutAccountPayload(protobuf.RevolutAccountPayload.newBuilder()
|
.setAccountId(accountId);
|
||||||
.setAccountId(accountId))
|
Optional.ofNullable(userName).ifPresent(revolutBuilder::setUserName);
|
||||||
.build();
|
return getPaymentAccountPayloadBuilder().setRevolutAccountPayload(revolutBuilder).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static RevolutAccountPayload fromProto(protobuf.PaymentAccountPayload proto) {
|
public static RevolutAccountPayload fromProto(protobuf.PaymentAccountPayload proto) {
|
||||||
|
protobuf.RevolutAccountPayload revolutAccountPayload = proto.getRevolutAccountPayload();
|
||||||
return new RevolutAccountPayload(proto.getPaymentMethodId(),
|
return new RevolutAccountPayload(proto.getPaymentMethodId(),
|
||||||
proto.getId(),
|
proto.getId(),
|
||||||
proto.getRevolutAccountPayload().getAccountId(),
|
revolutAccountPayload.getAccountId(),
|
||||||
|
ProtoUtil.stringOrNullFromProto(revolutAccountPayload.getUserName()),
|
||||||
proto.getMaxTradePeriod(),
|
proto.getMaxTradePeriod(),
|
||||||
new HashMap<>(proto.getExcludeFromJsonDataMap()));
|
new HashMap<>(proto.getExcludeFromJsonDataMap()));
|
||||||
}
|
}
|
||||||
@ -85,7 +100,7 @@ public final class RevolutAccountPayload extends PaymentAccountPayload {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getPaymentDetails() {
|
public String getPaymentDetails() {
|
||||||
return Res.get(paymentMethodId) + " - " + Res.getWithCol("payment.account") + " " + accountId;
|
return Res.get(paymentMethodId) + " - " + Res.getWithCol("payment.account.userName") + " " + getUserName();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -95,6 +110,24 @@ public final class RevolutAccountPayload extends PaymentAccountPayload {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getAgeWitnessInputData() {
|
public byte[] getAgeWitnessInputData() {
|
||||||
|
// getAgeWitnessInputData is called at new account creation when accountId is empty string.
|
||||||
return super.getAgeWitnessInputData(accountId.getBytes(StandardCharsets.UTF_8));
|
return super.getAgeWitnessInputData(accountId.getBytes(StandardCharsets.UTF_8));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUserName(@Nullable String userName) {
|
||||||
|
this.userName = userName;
|
||||||
|
// We only set accountId to userName for new accounts. Existing accounts have accountId set with email
|
||||||
|
// or phone nr. and we keep that to not break account signing.
|
||||||
|
if (accountId.isEmpty()) {
|
||||||
|
accountId = userName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserName() {
|
||||||
|
return userName != null ? userName : accountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean userNameNotSet() {
|
||||||
|
return userName == null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,6 @@ public class PriceFeedService {
|
|||||||
private final StringProperty currencyCodeProperty = new SimpleStringProperty();
|
private final StringProperty currencyCodeProperty = new SimpleStringProperty();
|
||||||
private final IntegerProperty updateCounter = new SimpleIntegerProperty(0);
|
private final IntegerProperty updateCounter = new SimpleIntegerProperty(0);
|
||||||
private long epochInMillisAtLastRequest;
|
private long epochInMillisAtLastRequest;
|
||||||
private Map<String, Long> timeStampMap = new HashMap<>();
|
|
||||||
private long retryDelay = 1;
|
private long retryDelay = 1;
|
||||||
private long requestTs;
|
private long requestTs;
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -126,6 +125,10 @@ public class PriceFeedService {
|
|||||||
request(false);
|
request(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasPrices() {
|
||||||
|
return !cache.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
public void requestPriceFeed(Consumer<Double> resultHandler, FaultHandler faultHandler) {
|
public void requestPriceFeed(Consumer<Double> resultHandler, FaultHandler faultHandler) {
|
||||||
this.priceConsumer = resultHandler;
|
this.priceConsumer = resultHandler;
|
||||||
this.faultHandler = faultHandler;
|
this.faultHandler = faultHandler;
|
||||||
@ -156,7 +159,7 @@ public class PriceFeedService {
|
|||||||
// At applyPriceToConsumer we also check if price is not exceeding max. age for price data.
|
// At applyPriceToConsumer we also check if price is not exceeding max. age for price data.
|
||||||
boolean success = applyPriceToConsumer();
|
boolean success = applyPriceToConsumer();
|
||||||
if (success) {
|
if (success) {
|
||||||
final MarketPrice marketPrice = cache.get(currencyCode);
|
MarketPrice marketPrice = cache.get(currencyCode);
|
||||||
if (marketPrice != null)
|
if (marketPrice != null)
|
||||||
log.debug("Received new {} from provider {} after {} sec.",
|
log.debug("Received new {} from provider {} after {} sec.",
|
||||||
marketPrice,
|
marketPrice,
|
||||||
@ -326,7 +329,7 @@ public class PriceFeedService {
|
|||||||
boolean result = false;
|
boolean result = false;
|
||||||
String errorMessage = null;
|
String errorMessage = null;
|
||||||
if (currencyCode != null) {
|
if (currencyCode != null) {
|
||||||
final String baseUrl = priceProvider.getBaseUrl();
|
String baseUrl = priceProvider.getBaseUrl();
|
||||||
if (cache.containsKey(currencyCode)) {
|
if (cache.containsKey(currencyCode)) {
|
||||||
try {
|
try {
|
||||||
MarketPrice marketPrice = cache.get(currencyCode);
|
MarketPrice marketPrice = cache.get(currencyCode);
|
||||||
@ -383,14 +386,12 @@ public class PriceFeedService {
|
|||||||
public void onSuccess(@Nullable Tuple2<Map<String, Long>, Map<String, MarketPrice>> result) {
|
public void onSuccess(@Nullable Tuple2<Map<String, Long>, Map<String, MarketPrice>> result) {
|
||||||
UserThread.execute(() -> {
|
UserThread.execute(() -> {
|
||||||
checkNotNull(result, "Result must not be null at requestAllPrices");
|
checkNotNull(result, "Result must not be null at requestAllPrices");
|
||||||
timeStampMap = result.first;
|
|
||||||
|
|
||||||
// Each currency rate has a different timestamp, depending on when
|
// Each currency rate has a different timestamp, depending on when
|
||||||
// the pricenode aggregate rate was calculated
|
// the pricenode aggregate rate was calculated
|
||||||
// However, the request timestamp is when the pricenode was queried
|
// However, the request timestamp is when the pricenode was queried
|
||||||
epochInMillisAtLastRequest = System.currentTimeMillis();
|
epochInMillisAtLastRequest = System.currentTimeMillis();
|
||||||
|
|
||||||
final Map<String, MarketPrice> priceMap = result.second;
|
Map<String, MarketPrice> priceMap = result.second;
|
||||||
|
|
||||||
cache.putAll(priceMap);
|
cache.putAll(priceMap);
|
||||||
|
|
||||||
|
@ -70,7 +70,24 @@ public class PriceProvider extends HttpClientProvider {
|
|||||||
final double price = (Double) treeMap.get("price");
|
final double price = (Double) treeMap.get("price");
|
||||||
// json uses double for our timestampSec long value...
|
// json uses double for our timestampSec long value...
|
||||||
final long timestampSec = MathUtils.doubleToLong((Double) treeMap.get("timestampSec"));
|
final long timestampSec = MathUtils.doubleToLong((Double) treeMap.get("timestampSec"));
|
||||||
marketPriceMap.put(currencyCode, new MarketPrice(currencyCode, price, timestampSec, true));
|
|
||||||
|
// We do not have support for the case of multiChain assets where a common price ticker used for
|
||||||
|
// different flavours of the asset. It would be quite a bit of effort to add generic support to the
|
||||||
|
// asset and tradeCurrency classes and to handle it correctly from their many client classes.
|
||||||
|
// So we decided to hack in the sub-assets as copies of the price and accept the annoyance to see
|
||||||
|
// 3 different prices for the same master asset. But who knows, maybe prices will differ over time for
|
||||||
|
// the sub assets so then we are better prepared that way...
|
||||||
|
if (currencyCode.equals("USDT")) {
|
||||||
|
addPrice(marketPriceMap, "USDT-O", price, timestampSec);
|
||||||
|
addPrice(marketPriceMap, "USDT-E", price, timestampSec);
|
||||||
|
addPrice(marketPriceMap, "L-USDT", price, timestampSec);
|
||||||
|
} else {
|
||||||
|
// NON_EXISTING_SYMBOL is returned from service for nto found items
|
||||||
|
// Sometimes it has post fixes as well so we use a 'contains' check.
|
||||||
|
if (!currencyCode.contains("NON_EXISTING_SYMBOL")) {
|
||||||
|
addPrice(marketPriceMap, currencyCode, price, timestampSec);
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
log.error(t.toString());
|
log.error(t.toString());
|
||||||
t.printStackTrace();
|
t.printStackTrace();
|
||||||
@ -80,6 +97,13 @@ public class PriceProvider extends HttpClientProvider {
|
|||||||
return new Tuple2<>(tsMap, marketPriceMap);
|
return new Tuple2<>(tsMap, marketPriceMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addPrice(Map<String, MarketPrice> marketPriceMap,
|
||||||
|
String currencyCode,
|
||||||
|
double price,
|
||||||
|
long timestampSec) {
|
||||||
|
marketPriceMap.put(currencyCode, new MarketPrice(currencyCode, price, timestampSec, true));
|
||||||
|
}
|
||||||
|
|
||||||
public String getBaseUrl() {
|
public String getBaseUrl() {
|
||||||
return httpClient.getBaseUrl();
|
return httpClient.getBaseUrl();
|
||||||
}
|
}
|
||||||
|
@ -62,10 +62,14 @@ public abstract class SupportManager {
|
|||||||
|
|
||||||
// We get first the message handler called then the onBootstrapped
|
// We get first the message handler called then the onBootstrapped
|
||||||
p2PService.addDecryptedDirectMessageListener((decryptedMessageWithPubKey, senderAddress) -> {
|
p2PService.addDecryptedDirectMessageListener((decryptedMessageWithPubKey, senderAddress) -> {
|
||||||
|
// As decryptedDirectMessageWithPubKeys is a CopyOnWriteArraySet we do not need to check if it was
|
||||||
|
// already stored
|
||||||
decryptedDirectMessageWithPubKeys.add(decryptedMessageWithPubKey);
|
decryptedDirectMessageWithPubKeys.add(decryptedMessageWithPubKey);
|
||||||
tryApplyMessages();
|
tryApplyMessages();
|
||||||
});
|
});
|
||||||
p2PService.addDecryptedMailboxListener((decryptedMessageWithPubKey, senderAddress) -> {
|
p2PService.addDecryptedMailboxListener((decryptedMessageWithPubKey, senderAddress) -> {
|
||||||
|
// As decryptedMailboxMessageWithPubKeys is a CopyOnWriteArraySet we do not need to check if it was
|
||||||
|
// already stored
|
||||||
decryptedMailboxMessageWithPubKeys.add(decryptedMessageWithPubKey);
|
decryptedMailboxMessageWithPubKeys.add(decryptedMessageWithPubKey);
|
||||||
tryApplyMessages();
|
tryApplyMessages();
|
||||||
});
|
});
|
||||||
|
@ -19,9 +19,16 @@ package bisq.core.support.dispute;
|
|||||||
|
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.btc.setup.WalletsSetup;
|
||||||
import bisq.core.btc.wallet.BtcWalletService;
|
import bisq.core.btc.wallet.BtcWalletService;
|
||||||
|
import bisq.core.btc.wallet.Restrictions;
|
||||||
import bisq.core.btc.wallet.TradeWalletService;
|
import bisq.core.btc.wallet.TradeWalletService;
|
||||||
|
import bisq.core.locale.CurrencyUtil;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
|
import bisq.core.monetary.Altcoin;
|
||||||
|
import bisq.core.monetary.Price;
|
||||||
|
import bisq.core.offer.OfferPayload;
|
||||||
import bisq.core.offer.OpenOfferManager;
|
import bisq.core.offer.OpenOfferManager;
|
||||||
|
import bisq.core.provider.price.MarketPrice;
|
||||||
|
import bisq.core.provider.price.PriceFeedService;
|
||||||
import bisq.core.support.SupportManager;
|
import bisq.core.support.SupportManager;
|
||||||
import bisq.core.support.dispute.messages.DisputeResultMessage;
|
import bisq.core.support.dispute.messages.DisputeResultMessage;
|
||||||
import bisq.core.support.dispute.messages.OpenNewDisputeMessage;
|
import bisq.core.support.dispute.messages.OpenNewDisputeMessage;
|
||||||
@ -37,13 +44,18 @@ import bisq.network.p2p.NodeAddress;
|
|||||||
import bisq.network.p2p.P2PService;
|
import bisq.network.p2p.P2PService;
|
||||||
import bisq.network.p2p.SendMailboxMessageListener;
|
import bisq.network.p2p.SendMailboxMessageListener;
|
||||||
|
|
||||||
|
import bisq.common.UserThread;
|
||||||
import bisq.common.app.Version;
|
import bisq.common.app.Version;
|
||||||
import bisq.common.crypto.PubKeyRing;
|
import bisq.common.crypto.PubKeyRing;
|
||||||
import bisq.common.handlers.FaultHandler;
|
import bisq.common.handlers.FaultHandler;
|
||||||
import bisq.common.handlers.ResultHandler;
|
import bisq.common.handlers.ResultHandler;
|
||||||
import bisq.common.storage.Storage;
|
import bisq.common.storage.Storage;
|
||||||
|
import bisq.common.util.MathUtils;
|
||||||
import bisq.common.util.Tuple2;
|
import bisq.common.util.Tuple2;
|
||||||
|
|
||||||
|
import org.bitcoinj.core.Coin;
|
||||||
|
import org.bitcoinj.utils.Fiat;
|
||||||
|
|
||||||
import javafx.beans.property.IntegerProperty;
|
import javafx.beans.property.IntegerProperty;
|
||||||
|
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
@ -51,6 +63,7 @@ import javafx.collections.ObservableList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -68,6 +81,7 @@ public abstract class DisputeManager<T extends DisputeList<? extends DisputeList
|
|||||||
protected final OpenOfferManager openOfferManager;
|
protected final OpenOfferManager openOfferManager;
|
||||||
protected final PubKeyRing pubKeyRing;
|
protected final PubKeyRing pubKeyRing;
|
||||||
protected final DisputeListService<T> disputeListService;
|
protected final DisputeListService<T> disputeListService;
|
||||||
|
private final PriceFeedService priceFeedService;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -82,7 +96,8 @@ public abstract class DisputeManager<T extends DisputeList<? extends DisputeList
|
|||||||
ClosedTradableManager closedTradableManager,
|
ClosedTradableManager closedTradableManager,
|
||||||
OpenOfferManager openOfferManager,
|
OpenOfferManager openOfferManager,
|
||||||
PubKeyRing pubKeyRing,
|
PubKeyRing pubKeyRing,
|
||||||
DisputeListService<T> disputeListService) {
|
DisputeListService<T> disputeListService,
|
||||||
|
PriceFeedService priceFeedService) {
|
||||||
super(p2PService, walletsSetup);
|
super(p2PService, walletsSetup);
|
||||||
|
|
||||||
this.tradeWalletService = tradeWalletService;
|
this.tradeWalletService = tradeWalletService;
|
||||||
@ -92,6 +107,7 @@ public abstract class DisputeManager<T extends DisputeList<? extends DisputeList
|
|||||||
this.openOfferManager = openOfferManager;
|
this.openOfferManager = openOfferManager;
|
||||||
this.pubKeyRing = pubKeyRing;
|
this.pubKeyRing = pubKeyRing;
|
||||||
this.disputeListService = disputeListService;
|
this.disputeListService = disputeListService;
|
||||||
|
this.priceFeedService = priceFeedService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -255,19 +271,20 @@ public abstract class DisputeManager<T extends DisputeList<? extends DisputeList
|
|||||||
|
|
||||||
String errorMessage = null;
|
String errorMessage = null;
|
||||||
Dispute dispute = openNewDisputeMessage.getDispute();
|
Dispute dispute = openNewDisputeMessage.getDispute();
|
||||||
|
dispute.setStorage(disputeListService.getStorage());
|
||||||
// Disputes from clients < 1.2.0 always have support type ARBITRATION in dispute as the field didn't exist before
|
// Disputes from clients < 1.2.0 always have support type ARBITRATION in dispute as the field didn't exist before
|
||||||
dispute.setSupportType(openNewDisputeMessage.getSupportType());
|
dispute.setSupportType(openNewDisputeMessage.getSupportType());
|
||||||
|
|
||||||
dispute.setStorage(disputeListService.getStorage());
|
Contract contract = dispute.getContract();
|
||||||
Contract contractFromOpener = dispute.getContract();
|
addPriceInfoMessage(dispute, 0);
|
||||||
PubKeyRing peersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contractFromOpener.getSellerPubKeyRing() : contractFromOpener.getBuyerPubKeyRing();
|
|
||||||
|
PubKeyRing peersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contract.getSellerPubKeyRing() : contract.getBuyerPubKeyRing();
|
||||||
if (isAgent(dispute)) {
|
if (isAgent(dispute)) {
|
||||||
if (!disputeList.contains(dispute)) {
|
if (!disputeList.contains(dispute)) {
|
||||||
Optional<Dispute> storedDisputeOptional = findDispute(dispute);
|
Optional<Dispute> storedDisputeOptional = findDispute(dispute);
|
||||||
if (!storedDisputeOptional.isPresent()) {
|
if (!storedDisputeOptional.isPresent()) {
|
||||||
disputeList.add(dispute);
|
disputeList.add(dispute);
|
||||||
errorMessage = sendPeerOpenedDisputeMessage(dispute, contractFromOpener, peersPubKeyRing);
|
sendPeerOpenedDisputeMessage(dispute, contract, peersPubKeyRing);
|
||||||
} else {
|
} else {
|
||||||
// valid case if both have opened a dispute and agent was not online.
|
// valid case if both have opened a dispute and agent was not online.
|
||||||
log.debug("We got a dispute already open for that trade and trading peer. TradeId = {}",
|
log.debug("We got a dispute already open for that trade and trading peer. TradeId = {}",
|
||||||
@ -286,23 +303,11 @@ public abstract class DisputeManager<T extends DisputeList<? extends DisputeList
|
|||||||
ObservableList<ChatMessage> messages = dispute.getChatMessages();
|
ObservableList<ChatMessage> messages = dispute.getChatMessages();
|
||||||
if (!messages.isEmpty()) {
|
if (!messages.isEmpty()) {
|
||||||
ChatMessage chatMessage = messages.get(0);
|
ChatMessage chatMessage = messages.get(0);
|
||||||
PubKeyRing sendersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contractFromOpener.getBuyerPubKeyRing() : contractFromOpener.getSellerPubKeyRing();
|
PubKeyRing sendersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contract.getBuyerPubKeyRing() : contract.getSellerPubKeyRing();
|
||||||
sendAckMessage(chatMessage, sendersPubKeyRing, errorMessage == null, errorMessage);
|
sendAckMessage(chatMessage, sendersPubKeyRing, errorMessage == null, errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// In case of refundAgent we add a message with the mediatorsDisputeSummary. Only visible for refundAgent.
|
addMediationResultMessage(dispute);
|
||||||
if (dispute.getMediatorsDisputeResult() != null) {
|
|
||||||
String mediatorsDisputeResult = Res.get("support.mediatorsDisputeSummary", dispute.getMediatorsDisputeResult());
|
|
||||||
ChatMessage mediatorsDisputeResultMessage = new ChatMessage(
|
|
||||||
getSupportType(),
|
|
||||||
dispute.getTradeId(),
|
|
||||||
pubKeyRing.hashCode(),
|
|
||||||
false,
|
|
||||||
mediatorsDisputeResult,
|
|
||||||
p2PService.getAddress());
|
|
||||||
mediatorsDisputeResultMessage.setSystemMessage(true);
|
|
||||||
dispute.addAndPersistChatMessage(mediatorsDisputeResultMessage);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// not dispute requester receives that from dispute agent
|
// not dispute requester receives that from dispute agent
|
||||||
@ -468,14 +473,27 @@ public abstract class DisputeManager<T extends DisputeList<? extends DisputeList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// dispute agent sends that to trading peer when he received openDispute request
|
// Dispute agent sends that to trading peer when he received openDispute request
|
||||||
private String sendPeerOpenedDisputeMessage(Dispute disputeFromOpener,
|
private void sendPeerOpenedDisputeMessage(Dispute disputeFromOpener,
|
||||||
|
Contract contractFromOpener,
|
||||||
|
PubKeyRing pubKeyRing) {
|
||||||
|
// We delay a bit for sending the message to the peer to allow that a openDispute message from the peer is
|
||||||
|
// being used as the valid msg. If dispute agent was offline and both peer requested we want to see the correct
|
||||||
|
// message and not skip the system message of the peer as it would be the case if we have created the system msg
|
||||||
|
// from the code below.
|
||||||
|
UserThread.runAfter(() -> doSendPeerOpenedDisputeMessage(disputeFromOpener,
|
||||||
|
contractFromOpener,
|
||||||
|
pubKeyRing),
|
||||||
|
100, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doSendPeerOpenedDisputeMessage(Dispute disputeFromOpener,
|
||||||
Contract contractFromOpener,
|
Contract contractFromOpener,
|
||||||
PubKeyRing pubKeyRing) {
|
PubKeyRing pubKeyRing) {
|
||||||
T disputeList = getDisputeList();
|
T disputeList = getDisputeList();
|
||||||
if (disputeList == null) {
|
if (disputeList == null) {
|
||||||
log.warn("disputes is null");
|
log.warn("disputes is null");
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Dispute dispute = new Dispute(disputeListService.getStorage(),
|
Dispute dispute = new Dispute(disputeListService.getStorage(),
|
||||||
@ -500,91 +518,94 @@ public abstract class DisputeManager<T extends DisputeList<? extends DisputeList
|
|||||||
dispute.setDelayedPayoutTxId(disputeFromOpener.getDelayedPayoutTxId());
|
dispute.setDelayedPayoutTxId(disputeFromOpener.getDelayedPayoutTxId());
|
||||||
|
|
||||||
Optional<Dispute> storedDisputeOptional = findDispute(dispute);
|
Optional<Dispute> storedDisputeOptional = findDispute(dispute);
|
||||||
if (!storedDisputeOptional.isPresent()) {
|
|
||||||
String disputeInfo = getDisputeInfo(dispute);
|
|
||||||
String disputeMessage = getDisputeIntroForPeer(disputeInfo);
|
|
||||||
String sysMsg = dispute.isSupportTicket() ?
|
|
||||||
Res.get("support.peerOpenedTicket", disputeInfo, Version.VERSION)
|
|
||||||
: disputeMessage;
|
|
||||||
ChatMessage chatMessage = new ChatMessage(
|
|
||||||
getSupportType(),
|
|
||||||
dispute.getTradeId(),
|
|
||||||
pubKeyRing.hashCode(),
|
|
||||||
false,
|
|
||||||
Res.get("support.systemMsg", sysMsg),
|
|
||||||
p2PService.getAddress());
|
|
||||||
chatMessage.setSystemMessage(true);
|
|
||||||
dispute.addAndPersistChatMessage(chatMessage);
|
|
||||||
disputeList.add(dispute);
|
|
||||||
|
|
||||||
// we mirrored dispute already!
|
// Valid case if both have opened a dispute and agent was not online.
|
||||||
Contract contract = dispute.getContract();
|
if (storedDisputeOptional.isPresent()) {
|
||||||
PubKeyRing peersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contract.getBuyerPubKeyRing() : contract.getSellerPubKeyRing();
|
log.info("We got a dispute already open for that trade and trading peer. TradeId = {}",
|
||||||
NodeAddress peersNodeAddress = dispute.isDisputeOpenerIsBuyer() ? contract.getBuyerNodeAddress() : contract.getSellerNodeAddress();
|
|
||||||
PeerOpenedDisputeMessage peerOpenedDisputeMessage = new PeerOpenedDisputeMessage(dispute,
|
|
||||||
p2PService.getAddress(),
|
|
||||||
UUID.randomUUID().toString(),
|
|
||||||
getSupportType());
|
|
||||||
|
|
||||||
log.info("Send {} to peer {}. tradeId={}, peerOpenedDisputeMessage.uid={}, chatMessage.uid={}",
|
|
||||||
peerOpenedDisputeMessage.getClass().getSimpleName(), peersNodeAddress,
|
|
||||||
peerOpenedDisputeMessage.getTradeId(), peerOpenedDisputeMessage.getUid(),
|
|
||||||
chatMessage.getUid());
|
|
||||||
|
|
||||||
p2PService.sendEncryptedMailboxMessage(peersNodeAddress,
|
|
||||||
peersPubKeyRing,
|
|
||||||
peerOpenedDisputeMessage,
|
|
||||||
new SendMailboxMessageListener() {
|
|
||||||
@Override
|
|
||||||
public void onArrived() {
|
|
||||||
log.info("{} arrived at peer {}. tradeId={}, peerOpenedDisputeMessage.uid={}, " +
|
|
||||||
"chatMessage.uid={}",
|
|
||||||
peerOpenedDisputeMessage.getClass().getSimpleName(), peersNodeAddress,
|
|
||||||
peerOpenedDisputeMessage.getTradeId(), peerOpenedDisputeMessage.getUid(),
|
|
||||||
chatMessage.getUid());
|
|
||||||
|
|
||||||
// We use the chatMessage wrapped inside the peerOpenedDisputeMessage for
|
|
||||||
// the state, as that is displayed to the user and we only persist that msg
|
|
||||||
chatMessage.setArrived(true);
|
|
||||||
disputeList.persist();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStoredInMailbox() {
|
|
||||||
log.info("{} stored in mailbox for peer {}. tradeId={}, peerOpenedDisputeMessage.uid={}, " +
|
|
||||||
"chatMessage.uid={}",
|
|
||||||
peerOpenedDisputeMessage.getClass().getSimpleName(), peersNodeAddress,
|
|
||||||
peerOpenedDisputeMessage.getTradeId(), peerOpenedDisputeMessage.getUid(),
|
|
||||||
chatMessage.getUid());
|
|
||||||
|
|
||||||
// We use the chatMessage wrapped inside the peerOpenedDisputeMessage for
|
|
||||||
// the state, as that is displayed to the user and we only persist that msg
|
|
||||||
chatMessage.setStoredInMailbox(true);
|
|
||||||
disputeList.persist();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFault(String errorMessage) {
|
|
||||||
log.error("{} failed: Peer {}. tradeId={}, peerOpenedDisputeMessage.uid={}, " +
|
|
||||||
"chatMessage.uid={}, errorMessage={}",
|
|
||||||
peerOpenedDisputeMessage.getClass().getSimpleName(), peersNodeAddress,
|
|
||||||
peerOpenedDisputeMessage.getTradeId(), peerOpenedDisputeMessage.getUid(),
|
|
||||||
chatMessage.getUid(), errorMessage);
|
|
||||||
|
|
||||||
// We use the chatMessage wrapped inside the peerOpenedDisputeMessage for
|
|
||||||
// the state, as that is displayed to the user and we only persist that msg
|
|
||||||
chatMessage.setSendMessageError(errorMessage);
|
|
||||||
disputeList.persist();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
// valid case if both have opened a dispute and agent was not online.
|
|
||||||
log.debug("We got a dispute already open for that trade and trading peer. TradeId = {}",
|
|
||||||
dispute.getTradeId());
|
dispute.getTradeId());
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String disputeInfo = getDisputeInfo(dispute);
|
||||||
|
String disputeMessage = getDisputeIntroForPeer(disputeInfo);
|
||||||
|
String sysMsg = dispute.isSupportTicket() ?
|
||||||
|
Res.get("support.peerOpenedTicket", disputeInfo, Version.VERSION)
|
||||||
|
: disputeMessage;
|
||||||
|
ChatMessage chatMessage = new ChatMessage(
|
||||||
|
getSupportType(),
|
||||||
|
dispute.getTradeId(),
|
||||||
|
pubKeyRing.hashCode(),
|
||||||
|
false,
|
||||||
|
Res.get("support.systemMsg", sysMsg),
|
||||||
|
p2PService.getAddress());
|
||||||
|
chatMessage.setSystemMessage(true);
|
||||||
|
dispute.addAndPersistChatMessage(chatMessage);
|
||||||
|
|
||||||
|
addPriceInfoMessage(dispute, 0);
|
||||||
|
|
||||||
|
disputeList.add(dispute);
|
||||||
|
|
||||||
|
// We mirrored dispute already!
|
||||||
|
Contract contract = dispute.getContract();
|
||||||
|
PubKeyRing peersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contract.getBuyerPubKeyRing() : contract.getSellerPubKeyRing();
|
||||||
|
NodeAddress peersNodeAddress = dispute.isDisputeOpenerIsBuyer() ? contract.getBuyerNodeAddress() : contract.getSellerNodeAddress();
|
||||||
|
PeerOpenedDisputeMessage peerOpenedDisputeMessage = new PeerOpenedDisputeMessage(dispute,
|
||||||
|
p2PService.getAddress(),
|
||||||
|
UUID.randomUUID().toString(),
|
||||||
|
getSupportType());
|
||||||
|
|
||||||
|
log.info("Send {} to peer {}. tradeId={}, peerOpenedDisputeMessage.uid={}, chatMessage.uid={}",
|
||||||
|
peerOpenedDisputeMessage.getClass().getSimpleName(), peersNodeAddress,
|
||||||
|
peerOpenedDisputeMessage.getTradeId(), peerOpenedDisputeMessage.getUid(),
|
||||||
|
chatMessage.getUid());
|
||||||
|
|
||||||
|
p2PService.sendEncryptedMailboxMessage(peersNodeAddress,
|
||||||
|
peersPubKeyRing,
|
||||||
|
peerOpenedDisputeMessage,
|
||||||
|
new SendMailboxMessageListener() {
|
||||||
|
@Override
|
||||||
|
public void onArrived() {
|
||||||
|
log.info("{} arrived at peer {}. tradeId={}, peerOpenedDisputeMessage.uid={}, " +
|
||||||
|
"chatMessage.uid={}",
|
||||||
|
peerOpenedDisputeMessage.getClass().getSimpleName(), peersNodeAddress,
|
||||||
|
peerOpenedDisputeMessage.getTradeId(), peerOpenedDisputeMessage.getUid(),
|
||||||
|
chatMessage.getUid());
|
||||||
|
|
||||||
|
// We use the chatMessage wrapped inside the peerOpenedDisputeMessage for
|
||||||
|
// the state, as that is displayed to the user and we only persist that msg
|
||||||
|
chatMessage.setArrived(true);
|
||||||
|
disputeList.persist();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStoredInMailbox() {
|
||||||
|
log.info("{} stored in mailbox for peer {}. tradeId={}, peerOpenedDisputeMessage.uid={}, " +
|
||||||
|
"chatMessage.uid={}",
|
||||||
|
peerOpenedDisputeMessage.getClass().getSimpleName(), peersNodeAddress,
|
||||||
|
peerOpenedDisputeMessage.getTradeId(), peerOpenedDisputeMessage.getUid(),
|
||||||
|
chatMessage.getUid());
|
||||||
|
|
||||||
|
// We use the chatMessage wrapped inside the peerOpenedDisputeMessage for
|
||||||
|
// the state, as that is displayed to the user and we only persist that msg
|
||||||
|
chatMessage.setStoredInMailbox(true);
|
||||||
|
disputeList.persist();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFault(String errorMessage) {
|
||||||
|
log.error("{} failed: Peer {}. tradeId={}, peerOpenedDisputeMessage.uid={}, " +
|
||||||
|
"chatMessage.uid={}, errorMessage={}",
|
||||||
|
peerOpenedDisputeMessage.getClass().getSimpleName(), peersNodeAddress,
|
||||||
|
peerOpenedDisputeMessage.getTradeId(), peerOpenedDisputeMessage.getUid(),
|
||||||
|
chatMessage.getUid(), errorMessage);
|
||||||
|
|
||||||
|
// We use the chatMessage wrapped inside the peerOpenedDisputeMessage for
|
||||||
|
// the state, as that is displayed to the user and we only persist that msg
|
||||||
|
chatMessage.setSendMessageError(errorMessage);
|
||||||
|
disputeList.persist();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// dispute agent send result to trader
|
// dispute agent send result to trader
|
||||||
@ -731,4 +752,112 @@ public abstract class DisputeManager<T extends DisputeList<? extends DisputeList
|
|||||||
.filter(e -> e.getTradeId().equals(tradeId))
|
.filter(e -> e.getTradeId().equals(tradeId))
|
||||||
.findAny();
|
.findAny();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addMediationResultMessage(Dispute dispute) {
|
||||||
|
// In case of refundAgent we add a message with the mediatorsDisputeSummary. Only visible for refundAgent.
|
||||||
|
if (dispute.getMediatorsDisputeResult() != null) {
|
||||||
|
String mediatorsDisputeResult = Res.get("support.mediatorsDisputeSummary", dispute.getMediatorsDisputeResult());
|
||||||
|
ChatMessage mediatorsDisputeResultMessage = new ChatMessage(
|
||||||
|
getSupportType(),
|
||||||
|
dispute.getTradeId(),
|
||||||
|
pubKeyRing.hashCode(),
|
||||||
|
false,
|
||||||
|
mediatorsDisputeResult,
|
||||||
|
p2PService.getAddress());
|
||||||
|
mediatorsDisputeResultMessage.setSystemMessage(true);
|
||||||
|
dispute.addAndPersistChatMessage(mediatorsDisputeResultMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If price was going down between take offer time and open dispute time the buyer has an incentive to
|
||||||
|
// not send the payment but to try to make a new trade with the better price. We risks to lose part of the
|
||||||
|
// security deposit (in mediation we will always get back 0.003 BTC to keep some incentive to accept mediated
|
||||||
|
// proposal). But if gain is larger than this loss he has economically an incentive to default in the trade.
|
||||||
|
// We do all those calculations to give a hint to mediators to detect option trades.
|
||||||
|
protected void addPriceInfoMessage(Dispute dispute, int counter) {
|
||||||
|
if (!priceFeedService.hasPrices()) {
|
||||||
|
if (counter < 3) {
|
||||||
|
log.info("Price provider has still no data. This is expected at startup. We try again in 10 sec.");
|
||||||
|
UserThread.runAfter(() -> addPriceInfoMessage(dispute, counter + 1), 10);
|
||||||
|
} else {
|
||||||
|
log.warn("Price provider still has no data after 3 repeated requests and 30 seconds delay. We give up.");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Contract contract = dispute.getContract();
|
||||||
|
OfferPayload offerPayload = contract.getOfferPayload();
|
||||||
|
Price priceAtDisputeOpening = getPrice(offerPayload.getCurrencyCode());
|
||||||
|
if (priceAtDisputeOpening == null) {
|
||||||
|
log.info("Price provider did not provide a price for {}. " +
|
||||||
|
"This is expected if this currency is not supported by the price providers.",
|
||||||
|
offerPayload.getCurrencyCode());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The amount we would get if we do a new trade with current price
|
||||||
|
Coin potentialAmountAtDisputeOpening = priceAtDisputeOpening.getAmountByVolume(contract.getTradeVolume());
|
||||||
|
Coin buyerSecurityDeposit = Coin.valueOf(offerPayload.getBuyerSecurityDeposit());
|
||||||
|
Coin minRefundAtMediatedDispute = Restrictions.getMinRefundAtMediatedDispute();
|
||||||
|
// minRefundAtMediatedDispute is always larger as buyerSecurityDeposit at mediated payout, we ignore refund agent case here as there it can be 0.
|
||||||
|
Coin maxLossSecDeposit = buyerSecurityDeposit.subtract(minRefundAtMediatedDispute);
|
||||||
|
Coin tradeAmount = contract.getTradeAmount();
|
||||||
|
Coin potentialGain = potentialAmountAtDisputeOpening.subtract(tradeAmount).subtract(maxLossSecDeposit);
|
||||||
|
String optionTradeDetails;
|
||||||
|
// We don't translate those strings (yet) as it is only displayed to mediators/arbitrators.
|
||||||
|
String headline;
|
||||||
|
if (potentialGain.isPositive()) {
|
||||||
|
headline = "This might be a potential option trade!";
|
||||||
|
optionTradeDetails = "\nBTC amount calculated with price at dispute opening: " + potentialAmountAtDisputeOpening.toFriendlyString() +
|
||||||
|
"\nMax loss of security deposit is: " + maxLossSecDeposit.toFriendlyString() +
|
||||||
|
"\nPossible gain from an option trade is: " + potentialGain.toFriendlyString();
|
||||||
|
} else {
|
||||||
|
headline = "It does not appear to be an option trade.";
|
||||||
|
optionTradeDetails = "\nBTC amount calculated with price at dispute opening: " + potentialAmountAtDisputeOpening.toFriendlyString() +
|
||||||
|
"\nMax loss of security deposit is: " + maxLossSecDeposit.toFriendlyString() +
|
||||||
|
"\nPossible loss from an option trade is: " + potentialGain.multiply(-1).toFriendlyString();
|
||||||
|
}
|
||||||
|
|
||||||
|
String percentagePriceDetails = offerPayload.isUseMarketBasedPrice() ?
|
||||||
|
" (market based price was used: " + offerPayload.getMarketPriceMargin() * 100 + "%)" :
|
||||||
|
" (fix price was used)";
|
||||||
|
|
||||||
|
String priceInfoText = "System message: " + headline +
|
||||||
|
"\n\nTrade price: " + contract.getTradePrice().toFriendlyString() + percentagePriceDetails +
|
||||||
|
"\nTrade amount: " + tradeAmount.toFriendlyString() +
|
||||||
|
"\nPrice at dispute opening: " + priceAtDisputeOpening.toFriendlyString() +
|
||||||
|
optionTradeDetails;
|
||||||
|
|
||||||
|
// We use the existing msg to copy over the users data
|
||||||
|
ChatMessage priceInfoMessage = new ChatMessage(
|
||||||
|
getSupportType(),
|
||||||
|
dispute.getTradeId(),
|
||||||
|
pubKeyRing.hashCode(),
|
||||||
|
false,
|
||||||
|
priceInfoText,
|
||||||
|
p2PService.getAddress());
|
||||||
|
priceInfoMessage.setSystemMessage(true);
|
||||||
|
dispute.addAndPersistChatMessage(priceInfoMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Price getPrice(String currencyCode) {
|
||||||
|
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
||||||
|
if (marketPrice != null && marketPrice.isRecentExternalPriceAvailable()) {
|
||||||
|
double marketPriceAsDouble = marketPrice.getPrice();
|
||||||
|
try {
|
||||||
|
int precision = CurrencyUtil.isCryptoCurrency(currencyCode) ?
|
||||||
|
Altcoin.SMALLEST_UNIT_EXPONENT :
|
||||||
|
Fiat.SMALLEST_UNIT_EXPONENT;
|
||||||
|
double scaled = MathUtils.scaleUpByPowerOf10(marketPriceAsDouble, precision);
|
||||||
|
long roundedToLong = MathUtils.roundDoubleToLong(scaled);
|
||||||
|
return Price.valueOf(currencyCode, roundedToLong);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Exception at getPrice / parseToFiat: " + e.toString());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ import bisq.core.btc.wallet.WalletService;
|
|||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.offer.OpenOffer;
|
import bisq.core.offer.OpenOffer;
|
||||||
import bisq.core.offer.OpenOfferManager;
|
import bisq.core.offer.OpenOfferManager;
|
||||||
|
import bisq.core.provider.price.PriceFeedService;
|
||||||
import bisq.core.support.SupportType;
|
import bisq.core.support.SupportType;
|
||||||
import bisq.core.support.dispute.Dispute;
|
import bisq.core.support.dispute.Dispute;
|
||||||
import bisq.core.support.dispute.DisputeManager;
|
import bisq.core.support.dispute.DisputeManager;
|
||||||
@ -88,9 +89,10 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
|||||||
ClosedTradableManager closedTradableManager,
|
ClosedTradableManager closedTradableManager,
|
||||||
OpenOfferManager openOfferManager,
|
OpenOfferManager openOfferManager,
|
||||||
PubKeyRing pubKeyRing,
|
PubKeyRing pubKeyRing,
|
||||||
ArbitrationDisputeListService arbitrationDisputeListService) {
|
ArbitrationDisputeListService arbitrationDisputeListService,
|
||||||
|
PriceFeedService priceFeedService) {
|
||||||
super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager,
|
super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager,
|
||||||
openOfferManager, pubKeyRing, arbitrationDisputeListService);
|
openOfferManager, pubKeyRing, arbitrationDisputeListService, priceFeedService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -163,6 +165,11 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
|||||||
return Res.get("support.youOpenedDispute", disputeInfo, Version.VERSION);
|
return Res.get("support.youOpenedDispute", disputeInfo, Version.VERSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addPriceInfoMessage(Dispute dispute, int counter) {
|
||||||
|
// Arbitrator is not used anymore.
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Message handler
|
// Message handler
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -23,6 +23,7 @@ import bisq.core.btc.wallet.TradeWalletService;
|
|||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.offer.OpenOffer;
|
import bisq.core.offer.OpenOffer;
|
||||||
import bisq.core.offer.OpenOfferManager;
|
import bisq.core.offer.OpenOfferManager;
|
||||||
|
import bisq.core.provider.price.PriceFeedService;
|
||||||
import bisq.core.support.SupportType;
|
import bisq.core.support.SupportType;
|
||||||
import bisq.core.support.dispute.Dispute;
|
import bisq.core.support.dispute.Dispute;
|
||||||
import bisq.core.support.dispute.DisputeManager;
|
import bisq.core.support.dispute.DisputeManager;
|
||||||
@ -80,9 +81,10 @@ public final class MediationManager extends DisputeManager<MediationDisputeList>
|
|||||||
ClosedTradableManager closedTradableManager,
|
ClosedTradableManager closedTradableManager,
|
||||||
OpenOfferManager openOfferManager,
|
OpenOfferManager openOfferManager,
|
||||||
PubKeyRing pubKeyRing,
|
PubKeyRing pubKeyRing,
|
||||||
MediationDisputeListService mediationDisputeListService) {
|
MediationDisputeListService mediationDisputeListService,
|
||||||
|
PriceFeedService priceFeedService) {
|
||||||
super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager,
|
super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager,
|
||||||
openOfferManager, pubKeyRing, mediationDisputeListService);
|
openOfferManager, pubKeyRing, mediationDisputeListService, priceFeedService);
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -23,6 +23,7 @@ import bisq.core.btc.wallet.TradeWalletService;
|
|||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.offer.OpenOffer;
|
import bisq.core.offer.OpenOffer;
|
||||||
import bisq.core.offer.OpenOfferManager;
|
import bisq.core.offer.OpenOfferManager;
|
||||||
|
import bisq.core.provider.price.PriceFeedService;
|
||||||
import bisq.core.support.SupportType;
|
import bisq.core.support.SupportType;
|
||||||
import bisq.core.support.dispute.Dispute;
|
import bisq.core.support.dispute.Dispute;
|
||||||
import bisq.core.support.dispute.DisputeManager;
|
import bisq.core.support.dispute.DisputeManager;
|
||||||
@ -74,9 +75,10 @@ public final class RefundManager extends DisputeManager<RefundDisputeList> {
|
|||||||
ClosedTradableManager closedTradableManager,
|
ClosedTradableManager closedTradableManager,
|
||||||
OpenOfferManager openOfferManager,
|
OpenOfferManager openOfferManager,
|
||||||
PubKeyRing pubKeyRing,
|
PubKeyRing pubKeyRing,
|
||||||
RefundDisputeListService refundDisputeListService) {
|
RefundDisputeListService refundDisputeListService,
|
||||||
|
PriceFeedService priceFeedService) {
|
||||||
super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager,
|
super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager,
|
||||||
openOfferManager, pubKeyRing, refundDisputeListService);
|
openOfferManager, pubKeyRing, refundDisputeListService, priceFeedService);
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -140,6 +142,14 @@ public final class RefundManager extends DisputeManager<RefundDisputeList> {
|
|||||||
return Res.get("support.youOpenedDispute", disputeInfo, Version.VERSION);
|
return Res.get("support.youOpenedDispute", disputeInfo, Version.VERSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addPriceInfoMessage(Dispute dispute, int counter) {
|
||||||
|
// At refund agent we do not add the option trade price check as the time for dispute opening is not correct.
|
||||||
|
// In case of an option trade the mediator adds to the result summary message automatically the system message
|
||||||
|
// with the option trade detection info so the refund agent can see that as well.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Message handler
|
// Message handler
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -26,6 +26,8 @@ import bisq.common.app.DevEnv;
|
|||||||
|
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
@ -51,7 +53,7 @@ public class XmrTxProofModel {
|
|||||||
private final int confirmsRequired;
|
private final int confirmsRequired;
|
||||||
private final String serviceAddress;
|
private final String serviceAddress;
|
||||||
|
|
||||||
public XmrTxProofModel(Trade trade, String serviceAddress, int confirmsRequired) {
|
XmrTxProofModel(Trade trade, String serviceAddress, int confirmsRequired) {
|
||||||
this.serviceAddress = serviceAddress;
|
this.serviceAddress = serviceAddress;
|
||||||
this.confirmsRequired = confirmsRequired;
|
this.confirmsRequired = confirmsRequired;
|
||||||
Coin tradeAmount = trade.getTradeAmount();
|
Coin tradeAmount = trade.getTradeAmount();
|
||||||
@ -68,4 +70,25 @@ public class XmrTxProofModel {
|
|||||||
tradeDate = trade.getDate();
|
tradeDate = trade.getDate();
|
||||||
tradeId = trade.getId();
|
tradeId = trade.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used only for testing
|
||||||
|
@VisibleForTesting
|
||||||
|
XmrTxProofModel(String tradeId,
|
||||||
|
String txHash,
|
||||||
|
String txKey,
|
||||||
|
String recipientAddress,
|
||||||
|
long amount,
|
||||||
|
Date tradeDate,
|
||||||
|
int confirmsRequired,
|
||||||
|
String serviceAddress) {
|
||||||
|
|
||||||
|
this.tradeId = tradeId;
|
||||||
|
this.txHash = txHash;
|
||||||
|
this.txKey = txKey;
|
||||||
|
this.recipientAddress = recipientAddress;
|
||||||
|
this.amount = amount;
|
||||||
|
this.tradeDate = tradeDate;
|
||||||
|
this.confirmsRequired = confirmsRequired;
|
||||||
|
this.serviceAddress = serviceAddress;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ public class ApplyFilter extends TradeTask {
|
|||||||
} else if (filterManager.isPaymentMethodBanned(trade.getOffer().getPaymentMethod())) {
|
} else if (filterManager.isPaymentMethodBanned(trade.getOffer().getPaymentMethod())) {
|
||||||
failed("Payment method is banned.\n" +
|
failed("Payment method is banned.\n" +
|
||||||
"Payment method=" + trade.getOffer().getPaymentMethod().getId());
|
"Payment method=" + trade.getOffer().getPaymentMethod().getId());
|
||||||
} else if (filterManager.isPeersPaymentAccountDataAreBanned(paymentAccountPayload, appliedPaymentAccountFilter)) {
|
} else if (filterManager.arePeersPaymentAccountDataBanned(paymentAccountPayload, appliedPaymentAccountFilter)) {
|
||||||
failed("Other trader is banned by their trading account data.\n" +
|
failed("Other trader is banned by their trading account data.\n" +
|
||||||
"paymentAccountPayload=" + paymentAccountPayload.getPaymentDetails() + "\n" +
|
"paymentAccountPayload=" + paymentAccountPayload.getPaymentDetails() + "\n" +
|
||||||
"banFilter=" + appliedPaymentAccountFilter[0].toString());
|
"banFilter=" + appliedPaymentAccountFilter[0].toString());
|
||||||
|
@ -24,6 +24,7 @@ import bisq.core.locale.TradeCurrency;
|
|||||||
import bisq.core.notifications.alerts.market.MarketAlertFilter;
|
import bisq.core.notifications.alerts.market.MarketAlertFilter;
|
||||||
import bisq.core.notifications.alerts.price.PriceAlertFilter;
|
import bisq.core.notifications.alerts.price.PriceAlertFilter;
|
||||||
import bisq.core.payment.PaymentAccount;
|
import bisq.core.payment.PaymentAccount;
|
||||||
|
import bisq.core.payment.RevolutAccount;
|
||||||
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
|
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
|
||||||
import bisq.core.support.dispute.mediation.mediator.Mediator;
|
import bisq.core.support.dispute.mediation.mediator.Mediator;
|
||||||
import bisq.core.support.dispute.refund.refundagent.RefundAgent;
|
import bisq.core.support.dispute.refund.refundagent.RefundAgent;
|
||||||
@ -50,6 +51,7 @@ import java.util.HashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
@ -126,6 +128,16 @@ public class User implements PersistedDataHost {
|
|||||||
// API
|
// API
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public void onAllServicesInitialized(@Nullable Consumer<List<RevolutAccount>> resultHandler) {
|
||||||
|
if (resultHandler != null) {
|
||||||
|
resultHandler.accept(paymentAccountsAsObservable.stream()
|
||||||
|
.filter(paymentAccount -> paymentAccount instanceof RevolutAccount)
|
||||||
|
.map(paymentAccount -> (RevolutAccount) paymentAccount)
|
||||||
|
.filter(RevolutAccount::userNameNotSet)
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public Arbitrator getAcceptedArbitratorByAddress(NodeAddress nodeAddress) {
|
public Arbitrator getAcceptedArbitratorByAddress(NodeAddress nodeAddress) {
|
||||||
final List<Arbitrator> acceptedArbitrators = userPayload.getAcceptedArbitrators();
|
final List<Arbitrator> acceptedArbitrators = userPayload.getAcceptedArbitrators();
|
||||||
|
@ -1065,7 +1065,7 @@ support.youOpenedDisputeForMediation=You requested mediation.\n\n{0}\n\nBisq ver
|
|||||||
support.peerOpenedTicket=Your trading peer has requested support due to technical problems.\n\n{0}\n\nBisq version: {1}
|
support.peerOpenedTicket=Your trading peer has requested support due to technical problems.\n\n{0}\n\nBisq version: {1}
|
||||||
support.peerOpenedDispute=Your trading peer has requested a dispute.\n\n{0}\n\nBisq version: {1}
|
support.peerOpenedDispute=Your trading peer has requested a dispute.\n\n{0}\n\nBisq version: {1}
|
||||||
support.peerOpenedDisputeForMediation=Your trading peer has requested mediation.\n\n{0}\n\nBisq version: {1}
|
support.peerOpenedDisputeForMediation=Your trading peer has requested mediation.\n\n{0}\n\nBisq version: {1}
|
||||||
support.mediatorsDisputeSummary=System message:\nMediator''s dispute summary:\n{0}
|
support.mediatorsDisputeSummary=System message: Mediator''s dispute summary:\n{0}
|
||||||
support.mediatorsAddress=Mediator''s node address: {0}
|
support.mediatorsAddress=Mediator''s node address: {0}
|
||||||
|
|
||||||
|
|
||||||
@ -1200,8 +1200,8 @@ setting.about.support=Support Bisq
|
|||||||
setting.about.def=Bisq is not a company—it is a project open to the community. If you want to participate or support Bisq please follow the links below.
|
setting.about.def=Bisq is not a company—it is a project open to the community. If you want to participate or support Bisq please follow the links below.
|
||||||
setting.about.contribute=Contribute
|
setting.about.contribute=Contribute
|
||||||
setting.about.providers=Data providers
|
setting.about.providers=Data providers
|
||||||
+setting.about.apisWithFee=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices, and Bisq Mempool Nodes for mining fee estimation.
|
setting.about.apisWithFee=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices, and Bisq Mempool Nodes for mining fee estimation.
|
||||||
+setting.about.apis=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices.
|
setting.about.apis=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices.
|
||||||
setting.about.pricesProvided=Market prices provided by
|
setting.about.pricesProvided=Market prices provided by
|
||||||
setting.about.feeEstimation.label=Mining fee estimation provided by
|
setting.about.feeEstimation.label=Mining fee estimation provided by
|
||||||
setting.about.versionDetails=Version details
|
setting.about.versionDetails=Version details
|
||||||
@ -2398,18 +2398,32 @@ disputeSummaryWindow.payoutAmount.buyer=Buyer's payout amount
|
|||||||
disputeSummaryWindow.payoutAmount.seller=Seller's payout amount
|
disputeSummaryWindow.payoutAmount.seller=Seller's payout amount
|
||||||
disputeSummaryWindow.payoutAmount.invert=Use loser as publisher
|
disputeSummaryWindow.payoutAmount.invert=Use loser as publisher
|
||||||
disputeSummaryWindow.reason=Reason of dispute
|
disputeSummaryWindow.reason=Reason of dispute
|
||||||
disputeSummaryWindow.reason.bug=Bug
|
|
||||||
disputeSummaryWindow.reason.usability=Usability
|
# dynamic values are not recognized by IntelliJ
|
||||||
disputeSummaryWindow.reason.protocolViolation=Protocol violation
|
# suppress inspection "UnusedProperty"
|
||||||
disputeSummaryWindow.reason.noReply=No reply
|
disputeSummaryWindow.reason.BUG=Bug
|
||||||
disputeSummaryWindow.reason.scam=Scam
|
# suppress inspection "UnusedProperty"
|
||||||
disputeSummaryWindow.reason.other=Other
|
disputeSummaryWindow.reason.USABILITY=Usability
|
||||||
disputeSummaryWindow.reason.bank=Bank
|
# suppress inspection "UnusedProperty"
|
||||||
disputeSummaryWindow.reason.optionTrade=Option trade
|
disputeSummaryWindow.reason.PROTOCOL_VIOLATION=Protocol violation
|
||||||
disputeSummaryWindow.reason.sellerNotResponding=Seller not responding
|
# suppress inspection "UnusedProperty"
|
||||||
disputeSummaryWindow.reason.wrongSenderAccount=Wrong sender account
|
disputeSummaryWindow.reason.NO_REPLY=No reply
|
||||||
disputeSummaryWindow.reason.peerWasLate=Peer was late
|
# suppress inspection "UnusedProperty"
|
||||||
disputeSummaryWindow.reason.tradeAlreadySettled=Trade already settled
|
disputeSummaryWindow.reason.SCAM=Scam
|
||||||
|
# suppress inspection "UnusedProperty"
|
||||||
|
disputeSummaryWindow.reason.OTHER=Other
|
||||||
|
# suppress inspection "UnusedProperty"
|
||||||
|
disputeSummaryWindow.reason.BANK_PROBLEMS=Bank
|
||||||
|
# suppress inspection "UnusedProperty"
|
||||||
|
disputeSummaryWindow.reason.OPTION_TRADE=Option trade
|
||||||
|
# suppress inspection "UnusedProperty"
|
||||||
|
disputeSummaryWindow.reason.SELLER_NOT_RESPONDING=Seller not responding
|
||||||
|
# suppress inspection "UnusedProperty"
|
||||||
|
disputeSummaryWindow.reason.WRONG_SENDER_ACCOUNT=Wrong sender account
|
||||||
|
# suppress inspection "UnusedProperty"
|
||||||
|
disputeSummaryWindow.reason.PEER_WAS_LATE=Peer was late
|
||||||
|
# suppress inspection "UnusedProperty"
|
||||||
|
disputeSummaryWindow.reason.TRADE_ALREADY_SETTLED=Trade already settled
|
||||||
|
|
||||||
disputeSummaryWindow.summaryNotes=Summary notes
|
disputeSummaryWindow.summaryNotes=Summary notes
|
||||||
disputeSummaryWindow.addSummaryNotes=Add summary notes
|
disputeSummaryWindow.addSummaryNotes=Add summary notes
|
||||||
@ -2418,7 +2432,8 @@ disputeSummaryWindow.close.msg=Ticket closed on {0}\n\n\
|
|||||||
Summary:\n\
|
Summary:\n\
|
||||||
Payout amount for BTC buyer: {1}\n\
|
Payout amount for BTC buyer: {1}\n\
|
||||||
Payout amount for BTC seller: {2}\n\n\
|
Payout amount for BTC seller: {2}\n\n\
|
||||||
Summary notes:\n{3}
|
Reason for dispute: {3}\n\n\
|
||||||
|
Summary notes:\n{4}
|
||||||
disputeSummaryWindow.close.nextStepsForMediation=\n\nNext steps:\n\
|
disputeSummaryWindow.close.nextStepsForMediation=\n\nNext steps:\n\
|
||||||
Open trade and accept or reject suggestion from mediator
|
Open trade and accept or reject suggestion from mediator
|
||||||
disputeSummaryWindow.close.nextStepsForRefundAgentArbitration=\n\nNext steps:\n\
|
disputeSummaryWindow.close.nextStepsForRefundAgentArbitration=\n\nNext steps:\n\
|
||||||
@ -2456,7 +2471,8 @@ filterWindow.onions=Filtered onion addresses (comma sep.)
|
|||||||
filterWindow.accounts=Filtered trading account data:\nFormat: comma sep. list of [payment method id | data field | value]
|
filterWindow.accounts=Filtered trading account data:\nFormat: comma sep. list of [payment method id | data field | value]
|
||||||
filterWindow.bannedCurrencies=Filtered currency codes (comma sep.)
|
filterWindow.bannedCurrencies=Filtered currency codes (comma sep.)
|
||||||
filterWindow.bannedPaymentMethods=Filtered payment method IDs (comma sep.)
|
filterWindow.bannedPaymentMethods=Filtered payment method IDs (comma sep.)
|
||||||
filterWindow.bannedSignerPubKeys=Filtered signer pubkeys (comma sep. hex of pubkeys)
|
filterWindow.bannedAccountWitnessSignerPubKeys=Filtered account witness signer pub keys (comma sep. hex of pub keys)
|
||||||
|
filterWindow.bannedPrivilegedDevPubKeys=Filtered privileged dev pub keys (comma sep. hex of pub keys)
|
||||||
filterWindow.arbitrators=Filtered arbitrators (comma sep. onion addresses)
|
filterWindow.arbitrators=Filtered arbitrators (comma sep. onion addresses)
|
||||||
filterWindow.mediators=Filtered mediators (comma sep. onion addresses)
|
filterWindow.mediators=Filtered mediators (comma sep. onion addresses)
|
||||||
filterWindow.refundAgents=Filtered refund agents (comma sep. onion addresses)
|
filterWindow.refundAgents=Filtered refund agents (comma sep. onion addresses)
|
||||||
@ -3014,6 +3030,7 @@ seed.restore.error=An error occurred when restoring the wallets with seed words.
|
|||||||
payment.account=Account
|
payment.account=Account
|
||||||
payment.account.no=Account no.
|
payment.account.no=Account no.
|
||||||
payment.account.name=Account name
|
payment.account.name=Account name
|
||||||
|
payment.account.userName=User name
|
||||||
payment.account.owner=Account owner full name
|
payment.account.owner=Account owner full name
|
||||||
payment.account.fullName=Full name (first, middle, last)
|
payment.account.fullName=Full name (first, middle, last)
|
||||||
payment.account.state=State/Province/Region
|
payment.account.state=State/Province/Region
|
||||||
@ -3048,8 +3065,6 @@ payment.cashApp.cashTag=$Cashtag
|
|||||||
payment.moneyBeam.accountId=Email or phone no.
|
payment.moneyBeam.accountId=Email or phone no.
|
||||||
payment.venmo.venmoUserName=Venmo username
|
payment.venmo.venmoUserName=Venmo username
|
||||||
payment.popmoney.accountId=Email or phone no.
|
payment.popmoney.accountId=Email or phone no.
|
||||||
payment.revolut.email=Email
|
|
||||||
payment.revolut.phoneNr=Registered phone no.
|
|
||||||
payment.promptPay.promptPayId=Citizen ID/Tax ID or phone no.
|
payment.promptPay.promptPayId=Citizen ID/Tax ID or phone no.
|
||||||
payment.supportedCurrencies=Supported currencies
|
payment.supportedCurrencies=Supported currencies
|
||||||
payment.limitations=Limitations
|
payment.limitations=Limitations
|
||||||
@ -3153,8 +3168,12 @@ payment.limits.info.withSigning=To limit chargeback risk, Bisq sets per-trade li
|
|||||||
payment.cashDeposit.info=Please confirm your bank allows you to send cash deposits into other peoples' accounts. \
|
payment.cashDeposit.info=Please confirm your bank allows you to send cash deposits into other peoples' accounts. \
|
||||||
For example, Bank of America and Wells Fargo no longer allow such deposits.
|
For example, Bank of America and Wells Fargo no longer allow such deposits.
|
||||||
|
|
||||||
payment.revolut.info=Please be sure that the phone number you used for your Revolut account is registered at Revolut \
|
payment.revolut.info=Revolut requires the 'User name' as account ID not the phone number or email as it was the case in the past.
|
||||||
otherwise the BTC buyer cannot send you the funds.
|
payment.account.revolut.addUserNameInfo={0}\n\
|
||||||
|
Your existing Revolut account ({1}) does not has set the ''User name''.\n\
|
||||||
|
Please enter your Revolut ''User name'' to update your account data.\n\
|
||||||
|
This will not affect your account age signing status.
|
||||||
|
payment.revolut.addUserNameInfo.headLine=Update Revolut account
|
||||||
|
|
||||||
payment.usPostalMoneyOrder.info=Trading using US Postal Money Orders (USPMO) on Bisq requires that you understand the following:\n\
|
payment.usPostalMoneyOrder.info=Trading using US Postal Money Orders (USPMO) on Bisq requires that you understand the following:\n\
|
||||||
\n\
|
\n\
|
||||||
|
@ -397,14 +397,14 @@ public class SignedWitnessServiceTest {
|
|||||||
signedWitnessService.addToMap(sw3);
|
signedWitnessService.addToMap(sw3);
|
||||||
|
|
||||||
// Second account is banned, first account is still a signer but the other two are no longer signers
|
// Second account is banned, first account is still a signer but the other two are no longer signers
|
||||||
when(filterManager.isSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner2PubKey))).thenReturn(true);
|
when(filterManager.isWitnessSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner2PubKey))).thenReturn(true);
|
||||||
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew1));
|
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew1));
|
||||||
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew2));
|
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew2));
|
||||||
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew3));
|
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew3));
|
||||||
|
|
||||||
// First account is banned, no accounts in the tree below it are signers
|
// First account is banned, no accounts in the tree below it are signers
|
||||||
when(filterManager.isSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner1PubKey))).thenReturn(true);
|
when(filterManager.isWitnessSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner1PubKey))).thenReturn(true);
|
||||||
when(filterManager.isSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner2PubKey))).thenReturn(false);
|
when(filterManager.isWitnessSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner2PubKey))).thenReturn(false);
|
||||||
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew1));
|
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew1));
|
||||||
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew2));
|
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew2));
|
||||||
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew3));
|
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew3));
|
||||||
@ -434,14 +434,14 @@ public class SignedWitnessServiceTest {
|
|||||||
signedWitnessService.addToMap(sw3);
|
signedWitnessService.addToMap(sw3);
|
||||||
|
|
||||||
// Only second account is banned, first account is still a signer but the other two are no longer signers
|
// Only second account is banned, first account is still a signer but the other two are no longer signers
|
||||||
when(filterManager.isSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner2PubKey))).thenReturn(true);
|
when(filterManager.isWitnessSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner2PubKey))).thenReturn(true);
|
||||||
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew1));
|
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew1));
|
||||||
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew2));
|
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew2));
|
||||||
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew3));
|
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew3));
|
||||||
|
|
||||||
// Only first account is banned, account2 and account3 are still signers
|
// Only first account is banned, account2 and account3 are still signers
|
||||||
when(filterManager.isSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner1PubKey))).thenReturn(true);
|
when(filterManager.isWitnessSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner1PubKey))).thenReturn(true);
|
||||||
when(filterManager.isSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner2PubKey))).thenReturn(false);
|
when(filterManager.isWitnessSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner2PubKey))).thenReturn(false);
|
||||||
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew1));
|
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew1));
|
||||||
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew2));
|
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew2));
|
||||||
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew3));
|
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew3));
|
||||||
@ -484,21 +484,21 @@ public class SignedWitnessServiceTest {
|
|||||||
signedWitnessService.addToMap(sw3p);
|
signedWitnessService.addToMap(sw3p);
|
||||||
|
|
||||||
// First account is banned, the other two are still signers
|
// First account is banned, the other two are still signers
|
||||||
when(filterManager.isSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner1PubKey))).thenReturn(true);
|
when(filterManager.isWitnessSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner1PubKey))).thenReturn(true);
|
||||||
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew1));
|
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew1));
|
||||||
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew2));
|
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew2));
|
||||||
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew3));
|
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew3));
|
||||||
|
|
||||||
// Second account is banned, the other two are still signers
|
// Second account is banned, the other two are still signers
|
||||||
when(filterManager.isSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner1PubKey))).thenReturn(false);
|
when(filterManager.isWitnessSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner1PubKey))).thenReturn(false);
|
||||||
when(filterManager.isSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner2PubKey))).thenReturn(true);
|
when(filterManager.isWitnessSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner2PubKey))).thenReturn(true);
|
||||||
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew1));
|
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew1));
|
||||||
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew2));
|
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew2));
|
||||||
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew3));
|
assertTrue(signedWitnessService.isSignerAccountAgeWitness(aew3));
|
||||||
|
|
||||||
// First and second account is banned, the third is no longer a signer
|
// First and second account is banned, the third is no longer a signer
|
||||||
when(filterManager.isSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner1PubKey))).thenReturn(true);
|
when(filterManager.isWitnessSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner1PubKey))).thenReturn(true);
|
||||||
when(filterManager.isSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner2PubKey))).thenReturn(true);
|
when(filterManager.isWitnessSignerPubKeyBanned(Utilities.bytesAsHexString(witnessOwner2PubKey))).thenReturn(true);
|
||||||
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew1));
|
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew1));
|
||||||
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew2));
|
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew2));
|
||||||
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew3));
|
assertFalse(signedWitnessService.isSignerAccountAgeWitness(aew3));
|
||||||
|
@ -218,8 +218,8 @@ public class AccountAgeWitnessServiceTest {
|
|||||||
when(filterManager.isNodeAddressBanned(any())).thenReturn(false);
|
when(filterManager.isNodeAddressBanned(any())).thenReturn(false);
|
||||||
when(filterManager.isCurrencyBanned(any())).thenReturn(false);
|
when(filterManager.isCurrencyBanned(any())).thenReturn(false);
|
||||||
when(filterManager.isPaymentMethodBanned(any())).thenReturn(false);
|
when(filterManager.isPaymentMethodBanned(any())).thenReturn(false);
|
||||||
when(filterManager.isPeersPaymentAccountDataAreBanned(any(), any())).thenReturn(false);
|
when(filterManager.arePeersPaymentAccountDataBanned(any(), any())).thenReturn(false);
|
||||||
when(filterManager.isSignerPubKeyBanned(any())).thenReturn(false);
|
when(filterManager.isWitnessSignerPubKeyBanned(any())).thenReturn(false);
|
||||||
|
|
||||||
when(chargeBackRisk.hasChargebackRisk(any(), any())).thenReturn(true);
|
when(chargeBackRisk.hasChargebackRisk(any(), any())).thenReturn(true);
|
||||||
|
|
||||||
|
@ -27,14 +27,14 @@ public class PriceTest {
|
|||||||
Price result = Price.parse("USD", "0.1");
|
Price result = Price.parse("USD", "0.1");
|
||||||
Assert.assertEquals(
|
Assert.assertEquals(
|
||||||
"Fiat value should be formatted with two decimals.",
|
"Fiat value should be formatted with two decimals.",
|
||||||
"0.10 USD",
|
"0.10 BTC/USD",
|
||||||
result.toFriendlyString()
|
result.toFriendlyString()
|
||||||
);
|
);
|
||||||
|
|
||||||
result = Price.parse("EUR", "0.1234");
|
result = Price.parse("EUR", "0.1234");
|
||||||
Assert.assertEquals(
|
Assert.assertEquals(
|
||||||
"Fiat value should be given two decimals",
|
"Fiat value should be given two decimals",
|
||||||
"0.1234 EUR",
|
"0.1234 BTC/EUR",
|
||||||
result.toFriendlyString()
|
result.toFriendlyString()
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -57,19 +57,19 @@ public class PriceTest {
|
|||||||
|
|
||||||
Assert.assertEquals(
|
Assert.assertEquals(
|
||||||
"Comma (',') as decimal separator should be converted to period ('.')",
|
"Comma (',') as decimal separator should be converted to period ('.')",
|
||||||
"0.0001 USD",
|
"0.0001 BTC/USD",
|
||||||
Price.parse("USD", "0,0001").toFriendlyString()
|
Price.parse("USD", "0,0001").toFriendlyString()
|
||||||
);
|
);
|
||||||
|
|
||||||
Assert.assertEquals(
|
Assert.assertEquals(
|
||||||
"Too many decimals should get rounded up properly.",
|
"Too many decimals should get rounded up properly.",
|
||||||
"10000.2346 LTC",
|
"10000.2346 LTC/BTC",
|
||||||
Price.parse("LTC", "10000,23456789").toFriendlyString()
|
Price.parse("LTC", "10000,23456789").toFriendlyString()
|
||||||
);
|
);
|
||||||
|
|
||||||
Assert.assertEquals(
|
Assert.assertEquals(
|
||||||
"Too many decimals should get rounded down properly.",
|
"Too many decimals should get rounded down properly.",
|
||||||
"10000.2345 LTC",
|
"10000.2345 LTC/BTC",
|
||||||
Price.parse("LTC", "10000,23454999").toFriendlyString()
|
Price.parse("LTC", "10000,23454999").toFriendlyString()
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -95,14 +95,14 @@ public class PriceTest {
|
|||||||
Price result = Price.valueOf("USD", 1);
|
Price result = Price.valueOf("USD", 1);
|
||||||
Assert.assertEquals(
|
Assert.assertEquals(
|
||||||
"Fiat value should have four decimals.",
|
"Fiat value should have four decimals.",
|
||||||
"0.0001 USD",
|
"0.0001 BTC/USD",
|
||||||
result.toFriendlyString()
|
result.toFriendlyString()
|
||||||
);
|
);
|
||||||
|
|
||||||
result = Price.valueOf("EUR", 1234);
|
result = Price.valueOf("EUR", 1234);
|
||||||
Assert.assertEquals(
|
Assert.assertEquals(
|
||||||
"Fiat value should be given two decimals",
|
"Fiat value should be given two decimals",
|
||||||
"0.1234 EUR",
|
"0.1234 BTC/EUR",
|
||||||
result.toFriendlyString()
|
result.toFriendlyString()
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -114,13 +114,13 @@ public class PriceTest {
|
|||||||
|
|
||||||
Assert.assertEquals(
|
Assert.assertEquals(
|
||||||
"Too many decimals should get rounded up properly.",
|
"Too many decimals should get rounded up properly.",
|
||||||
"10000.2346 LTC",
|
"10000.2346 LTC/BTC",
|
||||||
Price.valueOf("LTC", 1000023456789L).toFriendlyString()
|
Price.valueOf("LTC", 1000023456789L).toFriendlyString()
|
||||||
);
|
);
|
||||||
|
|
||||||
Assert.assertEquals(
|
Assert.assertEquals(
|
||||||
"Too many decimals should get rounded down properly.",
|
"Too many decimals should get rounded down properly.",
|
||||||
"10000.2345 LTC",
|
"10000.2345 LTC/BTC",
|
||||||
Price.valueOf("LTC", 1000023454999L).toFriendlyString()
|
Price.valueOf("LTC", 1000023454999L).toFriendlyString()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -51,23 +51,23 @@ public class XmrTxProofParserTest {
|
|||||||
public void testJsonTopLevel() {
|
public void testJsonTopLevel() {
|
||||||
// testing the top level fields: data and status
|
// testing the top level fields: data and status
|
||||||
assertTrue(XmrTxProofParser.parse(xmrTxProofModel,
|
assertTrue(XmrTxProofParser.parse(xmrTxProofModel,
|
||||||
"{'data':{'title':''},'status':'fail'}" )
|
"{'data':{'title':''},'status':'fail'}")
|
||||||
.getDetail() == XmrTxProofRequest.Detail.TX_NOT_FOUND);
|
.getDetail() == XmrTxProofRequest.Detail.TX_NOT_FOUND);
|
||||||
assertTrue(XmrTxProofParser.parse(xmrTxProofModel,
|
assertTrue(XmrTxProofParser.parse(xmrTxProofModel,
|
||||||
"{'data':{'title':''},'missingstatus':'success'}" )
|
"{'data':{'title':''},'missingstatus':'success'}")
|
||||||
.getDetail() == XmrTxProofRequest.Detail.API_INVALID);
|
.getDetail() == XmrTxProofRequest.Detail.API_INVALID);
|
||||||
assertTrue(XmrTxProofParser.parse(xmrTxProofModel,
|
assertTrue(XmrTxProofParser.parse(xmrTxProofModel,
|
||||||
"{'missingdata':{'title':''},'status':'success'}" )
|
"{'missingdata':{'title':''},'status':'success'}")
|
||||||
.getDetail() == XmrTxProofRequest.Detail.API_INVALID);
|
.getDetail() == XmrTxProofRequest.Detail.API_INVALID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testJsonAddress() {
|
public void testJsonAddress() {
|
||||||
assertTrue(XmrTxProofParser.parse(xmrTxProofModel,
|
assertTrue(XmrTxProofParser.parse(xmrTxProofModel,
|
||||||
"{'data':{'missingaddress':'irrelevant'},'status':'success'}" )
|
"{'data':{'missingaddress':'irrelevant'},'status':'success'}")
|
||||||
.getDetail() == XmrTxProofRequest.Detail.API_INVALID);
|
.getDetail() == XmrTxProofRequest.Detail.API_INVALID);
|
||||||
assertTrue(XmrTxProofParser.parse(xmrTxProofModel,
|
assertTrue(XmrTxProofParser.parse(xmrTxProofModel,
|
||||||
"{'data':{'address':'e957dac7'},'status':'success'}" )
|
"{'data':{'address':'e957dac7'},'status':'success'}")
|
||||||
.getDetail() == XmrTxProofRequest.Detail.ADDRESS_INVALID);
|
.getDetail() == XmrTxProofRequest.Detail.ADDRESS_INVALID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,14 +53,18 @@ public class UserPayloadModelVOTest {
|
|||||||
false,
|
false,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
"string",
|
Lists.newArrayList(),
|
||||||
new byte[]{10, 0, 0},
|
Lists.newArrayList(),
|
||||||
|
Lists.newArrayList(),
|
||||||
|
Lists.newArrayList(),
|
||||||
|
null,
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
null,
|
null,
|
||||||
Lists.newArrayList(),
|
|
||||||
Lists.newArrayList(),
|
|
||||||
Lists.newArrayList(),
|
|
||||||
Lists.newArrayList(),
|
|
||||||
false));
|
false));
|
||||||
|
|
||||||
vo.setRegisteredArbitrator(ArbitratorTest.getArbitratorMock());
|
vo.setRegisteredArbitrator(ArbitratorTest.getArbitratorMock());
|
||||||
vo.setRegisteredMediator(MediatorTest.getMediatorMock());
|
vo.setRegisteredMediator(MediatorTest.getMediatorMock());
|
||||||
vo.setAcceptedArbitrators(Lists.newArrayList(ArbitratorTest.getArbitratorMock()));
|
vo.setAcceptedArbitrators(Lists.newArrayList(ArbitratorTest.getArbitratorMock()));
|
||||||
|
@ -22,6 +22,7 @@ import bisq.core.dao.governance.param.Param;
|
|||||||
import bisq.core.filter.Filter;
|
import bisq.core.filter.Filter;
|
||||||
import bisq.core.filter.FilterManager;
|
import bisq.core.filter.FilterManager;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.primitives.Longs;
|
import com.google.common.primitives.Longs;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -98,10 +99,29 @@ public class FeeReceiverSelectorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static Filter filterWithReceivers(List<String> btcFeeReceiverAddresses) {
|
private static Filter filterWithReceivers(List<String> btcFeeReceiverAddresses) {
|
||||||
return new Filter(null, null, null, null,
|
return new Filter(Lists.newArrayList(),
|
||||||
null, null, null, null,
|
Lists.newArrayList(),
|
||||||
false, null, false, null,
|
Lists.newArrayList(),
|
||||||
null, null, null, null,
|
Lists.newArrayList(),
|
||||||
btcFeeReceiverAddresses, false);
|
Lists.newArrayList(),
|
||||||
|
Lists.newArrayList(),
|
||||||
|
Lists.newArrayList(),
|
||||||
|
Lists.newArrayList(),
|
||||||
|
false,
|
||||||
|
Lists.newArrayList(),
|
||||||
|
false,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
Lists.newArrayList(),
|
||||||
|
Lists.newArrayList(),
|
||||||
|
Lists.newArrayList(),
|
||||||
|
btcFeeReceiverAddresses,
|
||||||
|
null,
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,6 @@ import bisq.desktop.util.Layout;
|
|||||||
import bisq.desktop.util.validation.RevolutValidator;
|
import bisq.desktop.util.validation.RevolutValidator;
|
||||||
|
|
||||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||||
import bisq.core.locale.Country;
|
|
||||||
import bisq.core.locale.CountryUtil;
|
|
||||||
import bisq.core.locale.CurrencyUtil;
|
import bisq.core.locale.CurrencyUtil;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.payment.PaymentAccount;
|
import bisq.core.payment.PaymentAccount;
|
||||||
@ -34,46 +32,28 @@ import bisq.core.payment.payload.RevolutAccountPayload;
|
|||||||
import bisq.core.util.coin.CoinFormatter;
|
import bisq.core.util.coin.CoinFormatter;
|
||||||
import bisq.core.util.validation.InputValidator;
|
import bisq.core.util.validation.InputValidator;
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXComboBox;
|
|
||||||
|
|
||||||
import javafx.scene.control.ComboBox;
|
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
import javafx.scene.layout.FlowPane;
|
import javafx.scene.layout.FlowPane;
|
||||||
import javafx.scene.layout.GridPane;
|
import javafx.scene.layout.GridPane;
|
||||||
import javafx.scene.layout.HBox;
|
|
||||||
|
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
|
|
||||||
import javafx.util.StringConverter;
|
|
||||||
|
|
||||||
import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextField;
|
import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextField;
|
||||||
import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextFieldWithCopyIcon;
|
import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextFieldWithCopyIcon;
|
||||||
import static bisq.desktop.util.FormBuilder.addTopLabelFlowPane;
|
import static bisq.desktop.util.FormBuilder.addTopLabelFlowPane;
|
||||||
import static bisq.desktop.util.FormBuilder.addTopLabelTextField;
|
import static bisq.desktop.util.FormBuilder.addTopLabelTextField;
|
||||||
import static bisq.desktop.util.FormBuilder.addTopLabelWithVBox;
|
|
||||||
|
|
||||||
public class RevolutForm extends PaymentMethodForm {
|
public class RevolutForm extends PaymentMethodForm {
|
||||||
private final RevolutAccount account;
|
private final RevolutAccount account;
|
||||||
private RevolutValidator validator;
|
private RevolutValidator validator;
|
||||||
private InputTextField accountIdInputTextField;
|
private InputTextField userNameInputTextField;
|
||||||
private Country selectedCountry;
|
|
||||||
|
|
||||||
public static int addFormForBuyer(GridPane gridPane, int gridRow,
|
public static int addFormForBuyer(GridPane gridPane, int gridRow,
|
||||||
PaymentAccountPayload paymentAccountPayload) {
|
PaymentAccountPayload paymentAccountPayload) {
|
||||||
String accountId = ((RevolutAccountPayload) paymentAccountPayload).getAccountId();
|
String userName = ((RevolutAccountPayload) paymentAccountPayload).getUserName();
|
||||||
addCompactTopLabelTextFieldWithCopyIcon(gridPane, ++gridRow, getTitle(accountId), accountId);
|
addCompactTopLabelTextFieldWithCopyIcon(gridPane, ++gridRow, Res.get("payment.account.userName"), userName);
|
||||||
|
|
||||||
return gridRow;
|
return gridRow;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getTitle(String accountId) {
|
|
||||||
// From 0.9.4 on we only allow phone nr. as with emails we got too many disputes as users used an email which was
|
|
||||||
// not registered at Revolut. It seems that phone numbers need to be registered at least we have no reports from
|
|
||||||
// arbitrators with such cases. Thought email is still supported for backward compatibility.
|
|
||||||
// We might still get emails from users who have registered when email was supported
|
|
||||||
return accountId.contains("@") ? Res.get("payment.revolut.email") : Res.get("payment.revolut.phoneNr");
|
|
||||||
}
|
|
||||||
|
|
||||||
public RevolutForm(PaymentAccount paymentAccount, AccountAgeWitnessService accountAgeWitnessService,
|
public RevolutForm(PaymentAccount paymentAccount, AccountAgeWitnessService accountAgeWitnessService,
|
||||||
RevolutValidator revolutValidator, InputValidator inputValidator, GridPane gridPane,
|
RevolutValidator revolutValidator, InputValidator inputValidator, GridPane gridPane,
|
||||||
int gridRow, CoinFormatter formatter) {
|
int gridRow, CoinFormatter formatter) {
|
||||||
@ -86,63 +66,16 @@ public class RevolutForm extends PaymentMethodForm {
|
|||||||
public void addFormForAddAccount() {
|
public void addFormForAddAccount() {
|
||||||
gridRowFrom = gridRow + 1;
|
gridRowFrom = gridRow + 1;
|
||||||
|
|
||||||
// country selection is added only to prevent anymore email id input and
|
userNameInputTextField = FormBuilder.addInputTextField(gridPane, ++gridRow, Res.get("payment.account.userName"));
|
||||||
// solely to validate the given phone number
|
userNameInputTextField.setValidator(validator);
|
||||||
ComboBox<Country> countryComboBox = addCountrySelection();
|
userNameInputTextField.textProperty().addListener((ov, oldValue, newValue) -> {
|
||||||
setCountryComboBoxAction(countryComboBox);
|
account.setUserName(newValue.trim());
|
||||||
countryComboBox.setItems(FXCollections.observableArrayList(CountryUtil.getAllRevolutCountries()));
|
|
||||||
|
|
||||||
accountIdInputTextField = FormBuilder.addInputTextField(gridPane, ++gridRow, Res.get("payment.revolut.phoneNr"));
|
|
||||||
accountIdInputTextField.setValidator(validator);
|
|
||||||
accountIdInputTextField.textProperty().addListener((ov, oldValue, newValue) -> {
|
|
||||||
account.setAccountId(newValue.trim());
|
|
||||||
updateFromInputs();
|
updateFromInputs();
|
||||||
});
|
});
|
||||||
|
|
||||||
addCurrenciesGrid(true);
|
addCurrenciesGrid(true);
|
||||||
addLimitations(false);
|
addLimitations(false);
|
||||||
addAccountNameTextFieldWithAutoFillToggleButton();
|
addAccountNameTextFieldWithAutoFillToggleButton();
|
||||||
|
|
||||||
//set default country as selected
|
|
||||||
selectedCountry = CountryUtil.getDefaultCountry();
|
|
||||||
if (CountryUtil.getAllRevolutCountries().contains(selectedCountry)) {
|
|
||||||
countryComboBox.getSelectionModel().select(selectedCountry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ComboBox<Country> addCountrySelection() {
|
|
||||||
HBox hBox = new HBox();
|
|
||||||
|
|
||||||
hBox.setSpacing(5);
|
|
||||||
ComboBox<Country> countryComboBox = new JFXComboBox<>();
|
|
||||||
hBox.getChildren().add(countryComboBox);
|
|
||||||
|
|
||||||
addTopLabelWithVBox(gridPane, ++gridRow, Res.get("payment.bank.country"), hBox, 0);
|
|
||||||
|
|
||||||
countryComboBox.setPromptText(Res.get("payment.select.bank.country"));
|
|
||||||
countryComboBox.setConverter(new StringConverter<>() {
|
|
||||||
@Override
|
|
||||||
public String toString(Country country) {
|
|
||||||
return country.name + " (" + country.code + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Country fromString(String s) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return countryComboBox;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setCountryComboBoxAction(ComboBox<Country> countryComboBox) {
|
|
||||||
countryComboBox.setOnAction(e -> {
|
|
||||||
selectedCountry = countryComboBox.getSelectionModel().getSelectedItem();
|
|
||||||
updateFromInputs();
|
|
||||||
accountIdInputTextField.resetValidation();
|
|
||||||
accountIdInputTextField.validate();
|
|
||||||
accountIdInputTextField.requestFocus();
|
|
||||||
countryComboBox.requestFocus();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addCurrenciesGrid(boolean isEditable) {
|
private void addCurrenciesGrid(boolean isEditable) {
|
||||||
@ -161,18 +94,18 @@ public class RevolutForm extends PaymentMethodForm {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void autoFillNameTextField() {
|
protected void autoFillNameTextField() {
|
||||||
setAccountNameWithString(accountIdInputTextField.getText());
|
setAccountNameWithString(userNameInputTextField.getText());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addFormForDisplayAccount() {
|
public void addFormForDisplayAccount() {
|
||||||
gridRowFrom = gridRow;
|
gridRowFrom = gridRow;
|
||||||
addTopLabelTextField(gridPane, gridRow, Res.get("payment.account.name"),
|
addTopLabelTextField(gridPane, gridRow, Res.get("payment.account.userName"),
|
||||||
account.getAccountName(), Layout.FIRST_ROW_AND_GROUP_DISTANCE);
|
account.getAccountName(), Layout.FIRST_ROW_AND_GROUP_DISTANCE);
|
||||||
addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("shared.paymentMethod"),
|
addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("shared.paymentMethod"),
|
||||||
Res.get(account.getPaymentMethod().getId()));
|
Res.get(account.getPaymentMethod().getId()));
|
||||||
String accountId = account.getAccountId();
|
String userName = account.getUserName();
|
||||||
TextField field = addCompactTopLabelTextField(gridPane, ++gridRow, getTitle(accountId), accountId).second;
|
TextField field = addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("payment.account.userName"), userName).second;
|
||||||
field.setMouseTransparent(false);
|
field.setMouseTransparent(false);
|
||||||
addLimitations(true);
|
addLimitations(true);
|
||||||
addCurrenciesGrid(false);
|
addCurrenciesGrid(false);
|
||||||
@ -181,7 +114,7 @@ public class RevolutForm extends PaymentMethodForm {
|
|||||||
@Override
|
@Override
|
||||||
public void updateAllInputsValid() {
|
public void updateAllInputsValid() {
|
||||||
allInputsValid.set(isAccountNameValid()
|
allInputsValid.set(isAccountNameValid()
|
||||||
&& validator.validate(account.getAccountId(), selectedCountry.code).isValid
|
&& validator.validate(account.getUserName()).isValid
|
||||||
&& account.getTradeCurrencies().size() > 0);
|
&& account.getTradeCurrencies().size() > 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,12 +28,13 @@ import bisq.desktop.main.overlays.windows.DisplayAlertMessageWindow;
|
|||||||
import bisq.desktop.main.overlays.windows.NewTradeProtocolLaunchWindow;
|
import bisq.desktop.main.overlays.windows.NewTradeProtocolLaunchWindow;
|
||||||
import bisq.desktop.main.overlays.windows.TacWindow;
|
import bisq.desktop.main.overlays.windows.TacWindow;
|
||||||
import bisq.desktop.main.overlays.windows.TorNetworkSettingsWindow;
|
import bisq.desktop.main.overlays.windows.TorNetworkSettingsWindow;
|
||||||
|
import bisq.desktop.main.overlays.windows.UpdateRevolutAccountWindow;
|
||||||
import bisq.desktop.main.overlays.windows.WalletPasswordWindow;
|
import bisq.desktop.main.overlays.windows.WalletPasswordWindow;
|
||||||
import bisq.desktop.main.overlays.windows.downloadupdate.DisplayUpdateDownloadWindow;
|
import bisq.desktop.main.overlays.windows.downloadupdate.DisplayUpdateDownloadWindow;
|
||||||
import bisq.desktop.main.presentation.AccountPresentation;
|
import bisq.desktop.main.presentation.AccountPresentation;
|
||||||
import bisq.desktop.main.presentation.SettingsPresentation;
|
|
||||||
import bisq.desktop.main.presentation.DaoPresentation;
|
import bisq.desktop.main.presentation.DaoPresentation;
|
||||||
import bisq.desktop.main.presentation.MarketPricePresentation;
|
import bisq.desktop.main.presentation.MarketPricePresentation;
|
||||||
|
import bisq.desktop.main.presentation.SettingsPresentation;
|
||||||
import bisq.desktop.main.shared.PriceFeedComboBoxItem;
|
import bisq.desktop.main.shared.PriceFeedComboBoxItem;
|
||||||
import bisq.desktop.util.DisplayUtils;
|
import bisq.desktop.util.DisplayUtils;
|
||||||
import bisq.desktop.util.GUIUtil;
|
import bisq.desktop.util.GUIUtil;
|
||||||
@ -50,6 +51,7 @@ import bisq.core.locale.CurrencyUtil;
|
|||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.payment.AliPayAccount;
|
import bisq.core.payment.AliPayAccount;
|
||||||
import bisq.core.payment.CryptoCurrencyAccount;
|
import bisq.core.payment.CryptoCurrencyAccount;
|
||||||
|
import bisq.core.payment.RevolutAccount;
|
||||||
import bisq.core.presentation.BalancePresentation;
|
import bisq.core.presentation.BalancePresentation;
|
||||||
import bisq.core.presentation.SupportTicketsPresentation;
|
import bisq.core.presentation.SupportTicketsPresentation;
|
||||||
import bisq.core.presentation.TradePresentation;
|
import bisq.core.presentation.TradePresentation;
|
||||||
@ -88,12 +90,15 @@ import javafx.beans.property.StringProperty;
|
|||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.PriorityQueue;
|
import java.util.PriorityQueue;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -306,10 +311,11 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
|
|||||||
.useReportBugButton()
|
.useReportBugButton()
|
||||||
.show()));
|
.show()));
|
||||||
bisqSetup.setDisplayTorNetworkSettingsHandler(show -> {
|
bisqSetup.setDisplayTorNetworkSettingsHandler(show -> {
|
||||||
if (show)
|
if (show) {
|
||||||
torNetworkSettingsWindow.show();
|
torNetworkSettingsWindow.show();
|
||||||
else
|
} else if (torNetworkSettingsWindow.isDisplayed()) {
|
||||||
torNetworkSettingsWindow.hide();
|
torNetworkSettingsWindow.hide();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
bisqSetup.setSpvFileCorruptedHandler(msg -> new Popup().warning(msg)
|
bisqSetup.setSpvFileCorruptedHandler(msg -> new Popup().warning(msg)
|
||||||
.actionButtonText(Res.get("settings.net.reSyncSPVChainButton"))
|
.actionButtonText(Res.get("settings.net.reSyncSPVChainButton"))
|
||||||
@ -380,6 +386,12 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
|
|||||||
|
|
||||||
bisqSetup.setShowPopupIfInvalidBtcConfigHandler(this::showPopupIfInvalidBtcConfig);
|
bisqSetup.setShowPopupIfInvalidBtcConfigHandler(this::showPopupIfInvalidBtcConfig);
|
||||||
|
|
||||||
|
bisqSetup.setRevolutAccountsUpdateHandler(revolutAccountList -> {
|
||||||
|
// We copy the array as we will mutate it later
|
||||||
|
showRevolutAccountUpdateWindow(new ArrayList<>(revolutAccountList));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
corruptedDatabaseFilesHandler.getCorruptedDatabaseFiles().ifPresent(files -> new Popup()
|
corruptedDatabaseFilesHandler.getCorruptedDatabaseFiles().ifPresent(files -> new Popup()
|
||||||
.warning(Res.get("popup.warning.incompatibleDB", files.toString(), config.appDataDir))
|
.warning(Res.get("popup.warning.incompatibleDB", files.toString(), config.appDataDir))
|
||||||
.useShutDownButton()
|
.useShutDownButton()
|
||||||
@ -409,6 +421,17 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
|
|||||||
bisqSetup.setFilterWarningHandler(warning -> new Popup().warning(warning).show());
|
bisqSetup.setFilterWarningHandler(warning -> new Popup().warning(warning).show());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showRevolutAccountUpdateWindow(List<RevolutAccount> revolutAccountList) {
|
||||||
|
if (!revolutAccountList.isEmpty()) {
|
||||||
|
RevolutAccount revolutAccount = revolutAccountList.get(0);
|
||||||
|
revolutAccountList.remove(0);
|
||||||
|
new UpdateRevolutAccountWindow(revolutAccount, user).onClose(() -> {
|
||||||
|
// We delay a bit in case we have multiple account for better UX
|
||||||
|
UserThread.runAfter(() -> showRevolutAccountUpdateWindow(revolutAccountList), 300, TimeUnit.MILLISECONDS);
|
||||||
|
}).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void setupP2PNumPeersWatcher() {
|
private void setupP2PNumPeersWatcher() {
|
||||||
p2PService.getNumConnectedPeers().addListener((observable, oldValue, newValue) -> {
|
p2PService.getNumConnectedPeers().addListener((observable, oldValue, newValue) -> {
|
||||||
int numPeers = (int) newValue;
|
int numPeers = (int) newValue;
|
||||||
|
@ -50,7 +50,6 @@ import javafx.beans.property.ObjectProperty;
|
|||||||
import javafx.beans.property.SimpleIntegerProperty;
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
import javafx.beans.value.ObservableValue;
|
|
||||||
|
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
@ -127,14 +126,12 @@ class OfferBookChartViewModel extends ActivatableViewModel {
|
|||||||
fillTradeCurrencies();
|
fillTradeCurrencies();
|
||||||
};
|
};
|
||||||
|
|
||||||
currenciesUpdatedListener = new ChangeListener<>() {
|
currenciesUpdatedListener = (observable, oldValue, newValue) -> {
|
||||||
@Override
|
if (!isAnyPriceAbsent()) {
|
||||||
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
|
offerBook.fillOfferBookListItems();
|
||||||
if (!isAnyPricePresent()) {
|
updateChartData();
|
||||||
offerBook.fillOfferBookListItems();
|
var self = this;
|
||||||
updateChartData();
|
priceFeedService.updateCounterProperty().removeListener(self.currenciesUpdatedListener);
|
||||||
priceFeedService.updateCounterProperty().removeListener(currenciesUpdatedListener);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -163,7 +160,7 @@ class OfferBookChartViewModel extends ActivatableViewModel {
|
|||||||
fillTradeCurrencies();
|
fillTradeCurrencies();
|
||||||
updateChartData();
|
updateChartData();
|
||||||
|
|
||||||
if (isAnyPricePresent())
|
if (isAnyPriceAbsent())
|
||||||
priceFeedService.updateCounterProperty().addListener(currenciesUpdatedListener);
|
priceFeedService.updateCounterProperty().addListener(currenciesUpdatedListener);
|
||||||
|
|
||||||
syncPriceFeedCurrency();
|
syncPriceFeedCurrency();
|
||||||
@ -271,7 +268,7 @@ class OfferBookChartViewModel extends ActivatableViewModel {
|
|||||||
priceFeedService.setCurrencyCode(getCurrencyCode());
|
priceFeedService.setCurrencyCode(getCurrencyCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAnyPricePresent() {
|
private boolean isAnyPriceAbsent() {
|
||||||
return offerBookListItems.stream().anyMatch(item -> item.getOffer().getPrice() == null);
|
return offerBookListItems.stream().anyMatch(item -> item.getOffer().getPrice() == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,11 +288,11 @@ class OfferBookChartViewModel extends ActivatableViewModel {
|
|||||||
Comparator<Offer> offerAmountComparator = Comparator.comparing(Offer::getAmount).reversed();
|
Comparator<Offer> offerAmountComparator = Comparator.comparing(Offer::getAmount).reversed();
|
||||||
|
|
||||||
var buyOfferSortComparator =
|
var buyOfferSortComparator =
|
||||||
offerPriceComparator.reversed() // Buy offers, as opposed to sell offers, are primarily sorted from high price to low.
|
offerPriceComparator.reversed() // Buy offers, as opposed to sell offers, are primarily sorted from high price to low.
|
||||||
.thenComparing(offerAmountComparator);
|
.thenComparing(offerAmountComparator);
|
||||||
var sellOfferSortComparator =
|
var sellOfferSortComparator =
|
||||||
offerPriceComparator
|
offerPriceComparator
|
||||||
.thenComparing(offerAmountComparator);
|
.thenComparing(offerAmountComparator);
|
||||||
|
|
||||||
List<Offer> allBuyOffers = offerBookListItems.stream()
|
List<Offer> allBuyOffers = offerBookListItems.stream()
|
||||||
.map(OfferBookListItem::getOffer)
|
.map(OfferBookListItem::getOffer)
|
||||||
|
@ -71,7 +71,6 @@ import javax.inject.Named;
|
|||||||
import de.jensd.fx.glyphs.GlyphIcons;
|
import de.jensd.fx.glyphs.GlyphIcons;
|
||||||
import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon;
|
import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon;
|
||||||
|
|
||||||
import javafx.scene.Scene;
|
|
||||||
import javafx.scene.canvas.Canvas;
|
import javafx.scene.canvas.Canvas;
|
||||||
import javafx.scene.control.ComboBox;
|
import javafx.scene.control.ComboBox;
|
||||||
import javafx.scene.control.ContentDisplay;
|
import javafx.scene.control.ContentDisplay;
|
||||||
@ -731,6 +730,12 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
|||||||
return column;
|
return column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ObservableValue<OfferBookListItem> asPriceDependentObservable(OfferBookListItem item) {
|
||||||
|
return item.getOffer().isUseMarketBasedPrice()
|
||||||
|
? EasyBind.map(model.priceFeedService.updateCounterProperty(), n -> item)
|
||||||
|
: new ReadOnlyObjectWrapper<>(item);
|
||||||
|
}
|
||||||
|
|
||||||
private AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> getPriceColumn() {
|
private AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> getPriceColumn() {
|
||||||
AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> column = new AutoTooltipTableColumn<>("") {
|
AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> column = new AutoTooltipTableColumn<>("") {
|
||||||
{
|
{
|
||||||
@ -738,59 +743,20 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
column.getStyleClass().add("number-column");
|
column.getStyleClass().add("number-column");
|
||||||
column.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
|
column.setCellValueFactory(offer -> asPriceDependentObservable(offer.getValue()));
|
||||||
column.setCellFactory(
|
column.setCellFactory(
|
||||||
new Callback<>() {
|
new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public TableCell<OfferBookListItem, OfferBookListItem> call(
|
public TableCell<OfferBookListItem, OfferBookListItem> call(
|
||||||
TableColumn<OfferBookListItem, OfferBookListItem> column) {
|
TableColumn<OfferBookListItem, OfferBookListItem> column) {
|
||||||
return new TableCell<>() {
|
return new TableCell<>() {
|
||||||
private OfferBookListItem offerBookListItem;
|
|
||||||
private ChangeListener<Number> priceChangedListener;
|
|
||||||
ChangeListener<Scene> sceneChangeListener;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateItem(final OfferBookListItem item, boolean empty) {
|
public void updateItem(final OfferBookListItem item, boolean empty) {
|
||||||
super.updateItem(item, empty);
|
super.updateItem(item, empty);
|
||||||
|
|
||||||
if (item != null && !empty) {
|
if (item != null && !empty) {
|
||||||
if (getTableView().getScene() != null && sceneChangeListener == null) {
|
setGraphic(getPriceLabel(model.getPrice(item), item));
|
||||||
sceneChangeListener = (observable, oldValue, newValue) -> {
|
|
||||||
if (newValue == null) {
|
|
||||||
if (priceChangedListener != null) {
|
|
||||||
model.priceFeedService.updateCounterProperty().removeListener(priceChangedListener);
|
|
||||||
priceChangedListener = null;
|
|
||||||
}
|
|
||||||
offerBookListItem = null;
|
|
||||||
setGraphic(null);
|
|
||||||
getTableView().sceneProperty().removeListener(sceneChangeListener);
|
|
||||||
sceneChangeListener = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
getTableView().sceneProperty().addListener(sceneChangeListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.offerBookListItem = item;
|
|
||||||
|
|
||||||
if (priceChangedListener == null) {
|
|
||||||
priceChangedListener = (observable, oldValue, newValue) -> {
|
|
||||||
if (offerBookListItem != null && offerBookListItem.getOffer().getPrice() != null) {
|
|
||||||
setGraphic(getPriceLabel(model.getPrice(offerBookListItem), offerBookListItem));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
model.priceFeedService.updateCounterProperty().addListener(priceChangedListener);
|
|
||||||
}
|
|
||||||
setGraphic(getPriceLabel(item.getOffer().getPrice() == null ? Res.get("shared.na") : model.getPrice(item), item));
|
|
||||||
} else {
|
} else {
|
||||||
if (priceChangedListener != null) {
|
|
||||||
model.priceFeedService.updateCounterProperty().removeListener(priceChangedListener);
|
|
||||||
priceChangedListener = null;
|
|
||||||
}
|
|
||||||
if (sceneChangeListener != null) {
|
|
||||||
getTableView().sceneProperty().removeListener(sceneChangeListener);
|
|
||||||
sceneChangeListener = null;
|
|
||||||
}
|
|
||||||
this.offerBookListItem = null;
|
|
||||||
setGraphic(null);
|
setGraphic(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -845,35 +811,19 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
column.getStyleClass().add("number-column");
|
column.getStyleClass().add("number-column");
|
||||||
column.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
|
column.setCellValueFactory(offer -> asPriceDependentObservable(offer.getValue()));
|
||||||
column.setCellFactory(
|
column.setCellFactory(
|
||||||
new Callback<>() {
|
new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public TableCell<OfferBookListItem, OfferBookListItem> call(
|
public TableCell<OfferBookListItem, OfferBookListItem> call(
|
||||||
TableColumn<OfferBookListItem, OfferBookListItem> column) {
|
TableColumn<OfferBookListItem, OfferBookListItem> column) {
|
||||||
return new TableCell<>() {
|
return new TableCell<>() {
|
||||||
private OfferBookListItem offerBookListItem;
|
|
||||||
final ChangeListener<Number> listener = new ChangeListener<>() {
|
|
||||||
@Override
|
|
||||||
public void changed(ObservableValue<? extends Number> observable,
|
|
||||||
Number oldValue,
|
|
||||||
Number newValue) {
|
|
||||||
if (offerBookListItem != null && offerBookListItem.getOffer().getVolume() != null) {
|
|
||||||
setText("");
|
|
||||||
setGraphic(new ColoredDecimalPlacesWithZerosText(model.getVolume(offerBookListItem),
|
|
||||||
model.getNumberOfDecimalsForVolume(offerBookListItem)));
|
|
||||||
model.priceFeedService.updateCounterProperty().removeListener(listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateItem(final OfferBookListItem item, boolean empty) {
|
public void updateItem(final OfferBookListItem item, boolean empty) {
|
||||||
super.updateItem(item, empty);
|
super.updateItem(item, empty);
|
||||||
|
|
||||||
if (item != null && !empty) {
|
if (item != null && !empty) {
|
||||||
if (item.getOffer().getPrice() == null) {
|
if (item.getOffer().getPrice() == null) {
|
||||||
this.offerBookListItem = item;
|
|
||||||
model.priceFeedService.updateCounterProperty().addListener(listener);
|
|
||||||
setText(Res.get("shared.na"));
|
setText(Res.get("shared.na"));
|
||||||
setGraphic(null);
|
setGraphic(null);
|
||||||
} else {
|
} else {
|
||||||
@ -882,8 +832,6 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
|||||||
model.getNumberOfDecimalsForVolume(item)));
|
model.getNumberOfDecimalsForVolume(item)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
model.priceFeedService.updateCounterProperty().removeListener(listener);
|
|
||||||
this.offerBookListItem = null;
|
|
||||||
setText("");
|
setText("");
|
||||||
setGraphic(null);
|
setGraphic(null);
|
||||||
}
|
}
|
||||||
@ -1015,7 +963,7 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
|||||||
public void updateItem(final OfferBookListItem newItem, boolean empty) {
|
public void updateItem(final OfferBookListItem newItem, boolean empty) {
|
||||||
super.updateItem(newItem, empty);
|
super.updateItem(newItem, empty);
|
||||||
|
|
||||||
TableRow tableRow = getTableRow();
|
TableRow<OfferBookListItem> tableRow = getTableRow();
|
||||||
if (newItem != null && !empty) {
|
if (newItem != null && !empty) {
|
||||||
final Offer offer = newItem.getOffer();
|
final Offer offer = newItem.getOffer();
|
||||||
boolean myOffer = model.isMyOffer(offer);
|
boolean myOffer = model.isMyOffer(offer);
|
||||||
|
@ -486,18 +486,18 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void addReasonControls() {
|
private void addReasonControls() {
|
||||||
reasonWasBugRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.reason.bug"));
|
reasonWasBugRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.reason." + DisputeResult.Reason.BUG.name()));
|
||||||
reasonWasUsabilityIssueRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.reason.usability"));
|
reasonWasUsabilityIssueRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.reason." + DisputeResult.Reason.USABILITY.name()));
|
||||||
reasonProtocolViolationRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.reason.protocolViolation"));
|
reasonProtocolViolationRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.reason." + DisputeResult.Reason.PROTOCOL_VIOLATION.name()));
|
||||||
reasonNoReplyRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.reason.noReply"));
|
reasonNoReplyRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.reason." + DisputeResult.Reason.NO_REPLY.name()));
|
||||||
reasonWasScamRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.reason.scam"));
|
reasonWasScamRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.reason." + DisputeResult.Reason.SCAM.name()));
|
||||||
reasonWasBankRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.reason.bank"));
|
reasonWasBankRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.reason." + DisputeResult.Reason.BANK_PROBLEMS.name()));
|
||||||
reasonWasOtherRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.reason.other"));
|
reasonWasOtherRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.reason." + DisputeResult.Reason.OTHER.name()));
|
||||||
reasonWasOptionTradeRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.reason.optionTrade"));
|
reasonWasOptionTradeRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.reason." + DisputeResult.Reason.OPTION_TRADE.name()));
|
||||||
reasonWasSellerNotRespondingRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.reason.sellerNotResponding"));
|
reasonWasSellerNotRespondingRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.reason." + DisputeResult.Reason.SELLER_NOT_RESPONDING.name()));
|
||||||
reasonWasWrongSenderAccountRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.reason.wrongSenderAccount"));
|
reasonWasWrongSenderAccountRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.reason." + DisputeResult.Reason.WRONG_SENDER_ACCOUNT.name()));
|
||||||
reasonWasPeerWasLateRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.reason.peerWasLate"));
|
reasonWasPeerWasLateRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.reason." + DisputeResult.Reason.PEER_WAS_LATE.name()));
|
||||||
reasonWasTradeAlreadySettledRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.reason.tradeAlreadySettled"));
|
reasonWasTradeAlreadySettledRadioButton = new AutoTooltipRadioButton(Res.get("disputeSummaryWindow.reason." + DisputeResult.Reason.TRADE_ALREADY_SETTLED.name()));
|
||||||
|
|
||||||
HBox feeRadioButtonPane = new HBox();
|
HBox feeRadioButtonPane = new HBox();
|
||||||
feeRadioButtonPane.setSpacing(20);
|
feeRadioButtonPane.setSpacing(20);
|
||||||
@ -745,12 +745,20 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||||||
disputeResult.setCloseDate(new Date());
|
disputeResult.setCloseDate(new Date());
|
||||||
dispute.setDisputeResult(disputeResult);
|
dispute.setDisputeResult(disputeResult);
|
||||||
dispute.setIsClosed(true);
|
dispute.setIsClosed(true);
|
||||||
|
DisputeResult.Reason reason = disputeResult.getReason();
|
||||||
String text = Res.get("disputeSummaryWindow.close.msg",
|
String text = Res.get("disputeSummaryWindow.close.msg",
|
||||||
DisplayUtils.formatDateTime(disputeResult.getCloseDate()),
|
DisplayUtils.formatDateTime(disputeResult.getCloseDate()),
|
||||||
formatter.formatCoinWithCode(disputeResult.getBuyerPayoutAmount()),
|
formatter.formatCoinWithCode(disputeResult.getBuyerPayoutAmount()),
|
||||||
formatter.formatCoinWithCode(disputeResult.getSellerPayoutAmount()),
|
formatter.formatCoinWithCode(disputeResult.getSellerPayoutAmount()),
|
||||||
|
Res.get("disputeSummaryWindow.reason." + reason.name()),
|
||||||
disputeResult.summaryNotesProperty().get());
|
disputeResult.summaryNotesProperty().get());
|
||||||
|
|
||||||
|
if (reason == DisputeResult.Reason.OPTION_TRADE &&
|
||||||
|
dispute.getChatMessages().size() > 1 &&
|
||||||
|
dispute.getChatMessages().get(1).isSystemMessage()) {
|
||||||
|
text += "\n\n" + dispute.getChatMessages().get(1).getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
if (dispute.getSupportType() == SupportType.MEDIATION) {
|
if (dispute.getSupportType() == SupportType.MEDIATION) {
|
||||||
text += Res.get("disputeSummaryWindow.close.nextStepsForMediation");
|
text += Res.get("disputeSummaryWindow.close.nextStepsForMediation");
|
||||||
} else if (dispute.getSupportType() == SupportType.REFUND) {
|
} else if (dispute.getSupportType() == SupportType.REFUND) {
|
||||||
|
@ -79,7 +79,7 @@ public class FilterWindow extends Overlay<FilterWindow> {
|
|||||||
if (headLine == null)
|
if (headLine == null)
|
||||||
headLine = Res.get("filterWindow.headline");
|
headLine = Res.get("filterWindow.headline");
|
||||||
|
|
||||||
width = 968;
|
width = 1000;
|
||||||
|
|
||||||
createGridPane();
|
createGridPane();
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ public class FilterWindow extends Overlay<FilterWindow> {
|
|||||||
scrollPane.setContent(gridPane);
|
scrollPane.setContent(gridPane);
|
||||||
scrollPane.setFitToWidth(true);
|
scrollPane.setFitToWidth(true);
|
||||||
scrollPane.setFitToHeight(true);
|
scrollPane.setFitToHeight(true);
|
||||||
scrollPane.setMaxHeight(1000);
|
scrollPane.setMaxHeight(700);
|
||||||
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||||
|
|
||||||
addHeadLine();
|
addHeadLine();
|
||||||
@ -112,93 +112,127 @@ public class FilterWindow extends Overlay<FilterWindow> {
|
|||||||
gridPane.getColumnConstraints().remove(1);
|
gridPane.getColumnConstraints().remove(1);
|
||||||
gridPane.getColumnConstraints().get(0).setHalignment(HPos.LEFT);
|
gridPane.getColumnConstraints().get(0).setHalignment(HPos.LEFT);
|
||||||
|
|
||||||
InputTextField keyInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("shared.unlock"), 10);
|
InputTextField keyTF = addInputTextField(gridPane, ++rowIndex,
|
||||||
if (useDevPrivilegeKeys)
|
Res.get("shared.unlock"), 10);
|
||||||
keyInputTextField.setText(DevEnv.DEV_PRIVILEGE_PRIV_KEY);
|
if (useDevPrivilegeKeys) {
|
||||||
|
keyTF.setText(DevEnv.DEV_PRIVILEGE_PRIV_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
InputTextField offerIdsInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.offers"));
|
InputTextField offerIdsTF = addInputTextField(gridPane, ++rowIndex,
|
||||||
InputTextField nodesInputTextField = addTopLabelInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.onions")).second;
|
Res.get("filterWindow.offers"));
|
||||||
nodesInputTextField.setPromptText("E.g. zqnzx6o3nifef5df.onion:9999"); // Do not translate
|
InputTextField nodesTF = addTopLabelInputTextField(gridPane, ++rowIndex,
|
||||||
InputTextField paymentAccountFilterInputTextField = addTopLabelInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.accounts")).second;
|
Res.get("filterWindow.onions")).second;
|
||||||
GridPane.setHalignment(paymentAccountFilterInputTextField, HPos.RIGHT);
|
nodesTF.setPromptText("E.g. zqnzx6o3nifef5df.onion:9999"); // Do not translate
|
||||||
paymentAccountFilterInputTextField.setPromptText("E.g. PERFECT_MONEY|getAccountNr|12345"); // Do not translate
|
InputTextField paymentAccountFilterTF = addTopLabelInputTextField(gridPane, ++rowIndex,
|
||||||
InputTextField bannedCurrenciesInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.bannedCurrencies"));
|
Res.get("filterWindow.accounts")).second;
|
||||||
InputTextField bannedPaymentMethodsInputTextField = addTopLabelInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.bannedPaymentMethods")).second;
|
GridPane.setHalignment(paymentAccountFilterTF, HPos.RIGHT);
|
||||||
bannedPaymentMethodsInputTextField.setPromptText("E.g. PERFECT_MONEY"); // Do not translate
|
paymentAccountFilterTF.setPromptText("E.g. PERFECT_MONEY|getAccountNr|12345"); // Do not translate
|
||||||
InputTextField bannedSignerPubKeysInputTextField = addTopLabelInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.bannedSignerPubKeys")).second;
|
InputTextField bannedCurrenciesTF = addInputTextField(gridPane, ++rowIndex,
|
||||||
bannedSignerPubKeysInputTextField.setPromptText("E.g. 7f66117aa084e5a2c54fe17d29dd1fee2b241257"); // Do not translate
|
Res.get("filterWindow.bannedCurrencies"));
|
||||||
InputTextField arbitratorsInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.arbitrators"));
|
InputTextField bannedPaymentMethodsTF = addTopLabelInputTextField(gridPane, ++rowIndex,
|
||||||
InputTextField mediatorsInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.mediators"));
|
Res.get("filterWindow.bannedPaymentMethods")).second;
|
||||||
InputTextField refundAgentsInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.refundAgents"));
|
bannedPaymentMethodsTF.setPromptText("E.g. PERFECT_MONEY"); // Do not translate
|
||||||
InputTextField btcFeeReceiverAddressesInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.btcFeeReceiverAddresses"));
|
InputTextField bannedAccountWitnessSignerPubKeysTF = addTopLabelInputTextField(gridPane, ++rowIndex,
|
||||||
InputTextField seedNodesInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.seedNode"));
|
Res.get("filterWindow.bannedAccountWitnessSignerPubKeys")).second;
|
||||||
InputTextField priceRelayNodesInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.priceRelayNode"));
|
bannedAccountWitnessSignerPubKeysTF.setPromptText("E.g. 7f66117aa084e5a2c54fe17d29dd1fee2b241257"); // Do not translate
|
||||||
InputTextField btcNodesInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.btcNode"));
|
InputTextField arbitratorsTF = addInputTextField(gridPane, ++rowIndex,
|
||||||
CheckBox preventPublicBtcNetworkCheckBox = addLabelCheckBox(gridPane, ++rowIndex, Res.get("filterWindow.preventPublicBtcNetwork"));
|
Res.get("filterWindow.arbitrators"));
|
||||||
CheckBox disableDaoCheckBox = addLabelCheckBox(gridPane, ++rowIndex, Res.get("filterWindow.disableDao"));
|
InputTextField mediatorsTF = addInputTextField(gridPane, ++rowIndex,
|
||||||
CheckBox disableAutoConfCheckBox = addLabelCheckBox(gridPane, ++rowIndex, Res.get("filterWindow.disableAutoConf"));
|
Res.get("filterWindow.mediators"));
|
||||||
InputTextField disableDaoBelowVersionInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.disableDaoBelowVersion"));
|
InputTextField refundAgentsTF = addInputTextField(gridPane, ++rowIndex,
|
||||||
InputTextField disableTradeBelowVersionInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.disableTradeBelowVersion"));
|
Res.get("filterWindow.refundAgents"));
|
||||||
|
InputTextField btcFeeReceiverAddressesTF = addInputTextField(gridPane, ++rowIndex,
|
||||||
|
Res.get("filterWindow.btcFeeReceiverAddresses"));
|
||||||
|
InputTextField seedNodesTF = addInputTextField(gridPane, ++rowIndex,
|
||||||
|
Res.get("filterWindow.seedNode"));
|
||||||
|
InputTextField priceRelayNodesTF = addInputTextField(gridPane, ++rowIndex,
|
||||||
|
Res.get("filterWindow.priceRelayNode"));
|
||||||
|
InputTextField btcNodesTF = addInputTextField(gridPane, ++rowIndex,
|
||||||
|
Res.get("filterWindow.btcNode"));
|
||||||
|
CheckBox preventPublicBtcNetworkCheckBox = addLabelCheckBox(gridPane, ++rowIndex,
|
||||||
|
Res.get("filterWindow.preventPublicBtcNetwork"));
|
||||||
|
CheckBox disableDaoCheckBox = addLabelCheckBox(gridPane, ++rowIndex,
|
||||||
|
Res.get("filterWindow.disableDao"));
|
||||||
|
CheckBox disableAutoConfCheckBox = addLabelCheckBox(gridPane, ++rowIndex,
|
||||||
|
Res.get("filterWindow.disableAutoConf"));
|
||||||
|
InputTextField disableDaoBelowVersionTF = addInputTextField(gridPane, ++rowIndex,
|
||||||
|
Res.get("filterWindow.disableDaoBelowVersion"));
|
||||||
|
InputTextField disableTradeBelowVersionTF = addInputTextField(gridPane, ++rowIndex,
|
||||||
|
Res.get("filterWindow.disableTradeBelowVersion"));
|
||||||
|
InputTextField bannedPrivilegedDevPubKeysTF = addTopLabelInputTextField(gridPane, ++rowIndex,
|
||||||
|
Res.get("filterWindow.bannedPrivilegedDevPubKeys")).second;
|
||||||
|
|
||||||
final Filter filter = filterManager.getDevelopersFilter();
|
Filter filter = filterManager.getDevFilter();
|
||||||
if (filter != null) {
|
if (filter != null) {
|
||||||
setupFieldFromList(offerIdsInputTextField, filter.getBannedOfferIds());
|
setupFieldFromList(offerIdsTF, filter.getBannedOfferIds());
|
||||||
setupFieldFromList(nodesInputTextField, filter.getBannedNodeAddress());
|
setupFieldFromList(nodesTF, filter.getBannedNodeAddress());
|
||||||
setupFieldFromPaymentAccountFiltersList(paymentAccountFilterInputTextField, filter.getBannedPaymentAccounts());
|
setupFieldFromPaymentAccountFiltersList(paymentAccountFilterTF, filter.getBannedPaymentAccounts());
|
||||||
setupFieldFromList(bannedCurrenciesInputTextField, filter.getBannedCurrencies());
|
setupFieldFromList(bannedCurrenciesTF, filter.getBannedCurrencies());
|
||||||
setupFieldFromList(bannedPaymentMethodsInputTextField, filter.getBannedPaymentMethods());
|
setupFieldFromList(bannedPaymentMethodsTF, filter.getBannedPaymentMethods());
|
||||||
setupFieldFromList(bannedSignerPubKeysInputTextField, filter.getBannedSignerPubKeys());
|
setupFieldFromList(bannedAccountWitnessSignerPubKeysTF, filter.getBannedAccountWitnessSignerPubKeys());
|
||||||
setupFieldFromList(arbitratorsInputTextField, filter.getArbitrators());
|
setupFieldFromList(arbitratorsTF, filter.getArbitrators());
|
||||||
setupFieldFromList(mediatorsInputTextField, filter.getMediators());
|
setupFieldFromList(mediatorsTF, filter.getMediators());
|
||||||
setupFieldFromList(refundAgentsInputTextField, filter.getRefundAgents());
|
setupFieldFromList(refundAgentsTF, filter.getRefundAgents());
|
||||||
setupFieldFromList(btcFeeReceiverAddressesInputTextField, filter.getBtcFeeReceiverAddresses());
|
setupFieldFromList(btcFeeReceiverAddressesTF, filter.getBtcFeeReceiverAddresses());
|
||||||
setupFieldFromList(seedNodesInputTextField, filter.getSeedNodes());
|
setupFieldFromList(seedNodesTF, filter.getSeedNodes());
|
||||||
setupFieldFromList(priceRelayNodesInputTextField, filter.getPriceRelayNodes());
|
setupFieldFromList(priceRelayNodesTF, filter.getPriceRelayNodes());
|
||||||
setupFieldFromList(btcNodesInputTextField, filter.getBtcNodes());
|
setupFieldFromList(btcNodesTF, filter.getBtcNodes());
|
||||||
|
setupFieldFromList(bannedPrivilegedDevPubKeysTF, filter.getBannedPrivilegedDevPubKeys());
|
||||||
|
|
||||||
preventPublicBtcNetworkCheckBox.setSelected(filter.isPreventPublicBtcNetwork());
|
preventPublicBtcNetworkCheckBox.setSelected(filter.isPreventPublicBtcNetwork());
|
||||||
disableDaoCheckBox.setSelected(filter.isDisableDao());
|
disableDaoCheckBox.setSelected(filter.isDisableDao());
|
||||||
disableAutoConfCheckBox.setSelected(filter.isDisableAutoConf());
|
disableAutoConfCheckBox.setSelected(filter.isDisableAutoConf());
|
||||||
disableDaoBelowVersionInputTextField.setText(filter.getDisableDaoBelowVersion());
|
disableDaoBelowVersionTF.setText(filter.getDisableDaoBelowVersion());
|
||||||
disableTradeBelowVersionInputTextField.setText(filter.getDisableTradeBelowVersion());
|
disableTradeBelowVersionTF.setText(filter.getDisableTradeBelowVersion());
|
||||||
}
|
}
|
||||||
Button sendButton = new AutoTooltipButton(Res.get("filterWindow.add"));
|
|
||||||
sendButton.setOnAction(e -> {
|
|
||||||
if (filterManager.addFilterMessageIfKeyIsValid(
|
|
||||||
new Filter(
|
|
||||||
readAsList(offerIdsInputTextField),
|
|
||||||
readAsList(nodesInputTextField),
|
|
||||||
readAsPaymentAccountFiltersList(paymentAccountFilterInputTextField),
|
|
||||||
readAsList(bannedCurrenciesInputTextField),
|
|
||||||
readAsList(bannedPaymentMethodsInputTextField),
|
|
||||||
readAsList(arbitratorsInputTextField),
|
|
||||||
readAsList(seedNodesInputTextField),
|
|
||||||
readAsList(priceRelayNodesInputTextField),
|
|
||||||
preventPublicBtcNetworkCheckBox.isSelected(),
|
|
||||||
readAsList(btcNodesInputTextField),
|
|
||||||
disableDaoCheckBox.isSelected(),
|
|
||||||
disableDaoBelowVersionInputTextField.getText(),
|
|
||||||
disableTradeBelowVersionInputTextField.getText(),
|
|
||||||
readAsList(mediatorsInputTextField),
|
|
||||||
readAsList(refundAgentsInputTextField),
|
|
||||||
readAsList(bannedSignerPubKeysInputTextField),
|
|
||||||
readAsList(btcFeeReceiverAddressesInputTextField),
|
|
||||||
disableAutoConfCheckBox.isSelected()
|
|
||||||
),
|
|
||||||
keyInputTextField.getText())
|
|
||||||
)
|
|
||||||
hide();
|
|
||||||
else
|
|
||||||
new Popup().warning(Res.get("shared.invalidKey")).width(300).onClose(this::blurAgain).show();
|
|
||||||
});
|
|
||||||
|
|
||||||
Button removeFilterMessageButton = new AutoTooltipButton(Res.get("filterWindow.remove"));
|
Button removeFilterMessageButton = new AutoTooltipButton(Res.get("filterWindow.remove"));
|
||||||
|
removeFilterMessageButton.setDisable(filterManager.getDevFilter() == null);
|
||||||
|
|
||||||
|
Button sendButton = new AutoTooltipButton(Res.get("filterWindow.add"));
|
||||||
|
sendButton.setOnAction(e -> {
|
||||||
|
String privKeyString = keyTF.getText();
|
||||||
|
if (filterManager.canAddDevFilter(privKeyString)) {
|
||||||
|
String signerPubKeyAsHex = filterManager.getSignerPubKeyAsHex(privKeyString);
|
||||||
|
Filter newFilter = new Filter(
|
||||||
|
readAsList(offerIdsTF),
|
||||||
|
readAsList(nodesTF),
|
||||||
|
readAsPaymentAccountFiltersList(paymentAccountFilterTF),
|
||||||
|
readAsList(bannedCurrenciesTF),
|
||||||
|
readAsList(bannedPaymentMethodsTF),
|
||||||
|
readAsList(arbitratorsTF),
|
||||||
|
readAsList(seedNodesTF),
|
||||||
|
readAsList(priceRelayNodesTF),
|
||||||
|
preventPublicBtcNetworkCheckBox.isSelected(),
|
||||||
|
readAsList(btcNodesTF),
|
||||||
|
disableDaoCheckBox.isSelected(),
|
||||||
|
disableDaoBelowVersionTF.getText(),
|
||||||
|
disableTradeBelowVersionTF.getText(),
|
||||||
|
readAsList(mediatorsTF),
|
||||||
|
readAsList(refundAgentsTF),
|
||||||
|
readAsList(bannedAccountWitnessSignerPubKeysTF),
|
||||||
|
readAsList(btcFeeReceiverAddressesTF),
|
||||||
|
filterManager.getOwnerPubKey(),
|
||||||
|
signerPubKeyAsHex,
|
||||||
|
readAsList(bannedPrivilegedDevPubKeysTF),
|
||||||
|
disableAutoConfCheckBox.isSelected()
|
||||||
|
);
|
||||||
|
|
||||||
|
filterManager.addDevFilter(newFilter, privKeyString);
|
||||||
|
removeFilterMessageButton.setDisable(filterManager.getDevFilter() == null);
|
||||||
|
hide();
|
||||||
|
} else {
|
||||||
|
new Popup().warning(Res.get("shared.invalidKey")).onClose(this::blurAgain).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
removeFilterMessageButton.setOnAction(e -> {
|
removeFilterMessageButton.setOnAction(e -> {
|
||||||
if (keyInputTextField.getText().length() > 0) {
|
String privKeyString = keyTF.getText();
|
||||||
if (filterManager.removeFilterMessageIfKeyIsValid(keyInputTextField.getText()))
|
if (filterManager.canRemoveDevFilter(privKeyString)) {
|
||||||
hide();
|
filterManager.removeDevFilter(privKeyString);
|
||||||
else
|
hide();
|
||||||
new Popup().warning(Res.get("shared.invalidKey")).width(300).onClose(this::blurAgain).show();
|
} else {
|
||||||
|
new Popup().warning(Res.get("shared.invalidKey")).onClose(this::blurAgain).show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -218,13 +252,13 @@ public class FilterWindow extends Overlay<FilterWindow> {
|
|||||||
|
|
||||||
private void setupFieldFromList(InputTextField field, List<String> values) {
|
private void setupFieldFromList(InputTextField field, List<String> values) {
|
||||||
if (values != null)
|
if (values != null)
|
||||||
field.setText(values.stream().collect(Collectors.joining(", ")));
|
field.setText(String.join(", ", values));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupFieldFromPaymentAccountFiltersList(InputTextField field, List<PaymentAccountFilter> values) {
|
private void setupFieldFromPaymentAccountFiltersList(InputTextField field, List<PaymentAccountFilter> values) {
|
||||||
if (values != null) {
|
if (values != null) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
values.stream().forEach(e -> {
|
values.forEach(e -> {
|
||||||
if (e != null && e.getPaymentMethodId() != null) {
|
if (e != null && e.getPaymentMethodId() != null) {
|
||||||
sb
|
sb
|
||||||
.append(e.getPaymentMethodId())
|
.append(e.getPaymentMethodId())
|
||||||
|
@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* 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.desktop.main.overlays.windows;
|
||||||
|
|
||||||
|
import bisq.desktop.components.InputTextField;
|
||||||
|
import bisq.desktop.main.overlays.Overlay;
|
||||||
|
import bisq.desktop.util.Layout;
|
||||||
|
import bisq.desktop.util.validation.RevolutValidator;
|
||||||
|
|
||||||
|
import bisq.core.locale.Res;
|
||||||
|
import bisq.core.payment.RevolutAccount;
|
||||||
|
import bisq.core.user.User;
|
||||||
|
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
|
||||||
|
import static bisq.desktop.util.FormBuilder.addInputTextField;
|
||||||
|
import static bisq.desktop.util.FormBuilder.addLabel;
|
||||||
|
|
||||||
|
public class UpdateRevolutAccountWindow extends Overlay<UpdateRevolutAccountWindow> {
|
||||||
|
private final RevolutValidator revolutValidator;
|
||||||
|
private final RevolutAccount revolutAccount;
|
||||||
|
private final User user;
|
||||||
|
private InputTextField userNameInputTextField;
|
||||||
|
|
||||||
|
public UpdateRevolutAccountWindow(RevolutAccount revolutAccount, User user) {
|
||||||
|
super();
|
||||||
|
this.revolutAccount = revolutAccount;
|
||||||
|
this.user = user;
|
||||||
|
type = Type.Attention;
|
||||||
|
hideCloseButton = true;
|
||||||
|
revolutValidator = new RevolutValidator();
|
||||||
|
actionButtonText = Res.get("shared.save");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setupKeyHandler(Scene scene) {
|
||||||
|
// We do not support enter or escape here
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void show() {
|
||||||
|
if (headLine == null)
|
||||||
|
headLine = Res.get("payment.revolut.addUserNameInfo.headLine");
|
||||||
|
|
||||||
|
width = 868;
|
||||||
|
createGridPane();
|
||||||
|
addHeadLine();
|
||||||
|
addContent();
|
||||||
|
addButtons();
|
||||||
|
applyStyles();
|
||||||
|
display();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addContent() {
|
||||||
|
addLabel(gridPane, ++rowIndex, Res.get("payment.account.revolut.addUserNameInfo", Res.get("payment.revolut.info"), revolutAccount.getAccountName()));
|
||||||
|
userNameInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("payment.account.userName"), Layout.COMPACT_FIRST_ROW_DISTANCE);
|
||||||
|
userNameInputTextField.setValidator(revolutValidator);
|
||||||
|
userNameInputTextField.textProperty().addListener((observable, oldValue, newValue) ->
|
||||||
|
actionButton.setDisable(!revolutValidator.validate(newValue).isValid));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addButtons() {
|
||||||
|
super.addButtons();
|
||||||
|
|
||||||
|
// We do not allow close in case the userName is not correctly added so we
|
||||||
|
// overwrote the default handler
|
||||||
|
actionButton.setOnAction(event -> {
|
||||||
|
String userName = userNameInputTextField.getText();
|
||||||
|
if (revolutValidator.validate(userName).isValid) {
|
||||||
|
revolutAccount.setUserName(userName);
|
||||||
|
user.persist();
|
||||||
|
closeHandlerOptional.ifPresent(Runnable::run);
|
||||||
|
hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
actionButton.setDisable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -17,10 +17,17 @@
|
|||||||
|
|
||||||
package bisq.desktop.util.validation;
|
package bisq.desktop.util.validation;
|
||||||
|
|
||||||
public final class RevolutValidator extends PhoneNumberValidator {
|
public final class RevolutValidator extends LengthValidator {
|
||||||
|
public RevolutValidator() {
|
||||||
|
// Not sure what are requirements for Revolut user names
|
||||||
|
// Please keep in mind that even we force users to set user name at startup we should handle also the case
|
||||||
|
// that the old accountID as phone number or email is displayed at the username text field and we do not
|
||||||
|
// want to break validation in those cases. So being too strict on the validators might cause more troubles
|
||||||
|
// as its worth...
|
||||||
|
super(5, 100);
|
||||||
|
}
|
||||||
|
|
||||||
public ValidationResult validate(String input, String code) {
|
public ValidationResult validate(String input) {
|
||||||
super.setIsoCountryCode(code);
|
|
||||||
return super.validate(input);
|
return super.validate(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ dependencyVerification {
|
|||||||
'com.google.zxing:core:11aae8fd974ab25faa8208be50468eb12349cd239e93e7c797377fa13e381729',
|
'com.google.zxing:core:11aae8fd974ab25faa8208be50468eb12349cd239e93e7c797377fa13e381729',
|
||||||
'com.google.zxing:javase:0ec23e2ec12664ddd6347c8920ad647bb3b9da290f897a88516014b56cc77eb9',
|
'com.google.zxing:javase:0ec23e2ec12664ddd6347c8920ad647bb3b9da290f897a88516014b56cc77eb9',
|
||||||
'com.googlecode.jcsv:jcsv:73ca7d715e90c8d2c2635cc284543b038245a34f70790660ed590e157b8714a2',
|
'com.googlecode.jcsv:jcsv:73ca7d715e90c8d2c2635cc284543b038245a34f70790660ed590e157b8714a2',
|
||||||
'com.jfoenix:jfoenix:4739e37a05e67c3bc9d5b391a1b93717b5a48fa872992616b0964d3f827f8fe6',
|
'com.jfoenix:jfoenix:8060235fec5eb49617ec8d81d379e8c945f6cc722d0645e97190045100de2084',
|
||||||
'com.lambdaworks:scrypt:9a82d218099fb14c10c0e86e7eefeebd8c104de920acdc47b8b4b7a686fb73b4',
|
'com.lambdaworks:scrypt:9a82d218099fb14c10c0e86e7eefeebd8c104de920acdc47b8b4b7a686fb73b4',
|
||||||
'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f',
|
'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f',
|
||||||
'com.nativelibs4java:bridj:101bcd9b6637e6bc16e56deb3daefba62b1f5e8e9e37e1b3e56e3b5860d659cf',
|
'com.nativelibs4java:bridj:101bcd9b6637e6bc16e56deb3daefba62b1f5e8e9e37e1b3e56e3b5860d659cf',
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package bisq.network.p2p;
|
package bisq.network.p2p;
|
||||||
|
|
||||||
|
import bisq.network.p2p.storage.messages.BroadcastMessage;
|
||||||
import bisq.network.p2p.storage.payload.CapabilityRequiringPayload;
|
import bisq.network.p2p.storage.payload.CapabilityRequiringPayload;
|
||||||
|
|
||||||
import bisq.common.app.Capabilities;
|
import bisq.common.app.Capabilities;
|
||||||
@ -36,7 +37,7 @@ import lombok.Value;
|
|||||||
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@Value
|
@Value
|
||||||
public final class BundleOfEnvelopes extends NetworkEnvelope implements ExtendedDataSizePermission, CapabilityRequiringPayload {
|
public final class BundleOfEnvelopes extends BroadcastMessage implements ExtendedDataSizePermission, CapabilityRequiringPayload {
|
||||||
|
|
||||||
private final List<NetworkEnvelope> envelopes;
|
private final List<NetworkEnvelope> envelopes;
|
||||||
|
|
||||||
@ -44,6 +45,10 @@ public final class BundleOfEnvelopes extends NetworkEnvelope implements Extended
|
|||||||
this(new ArrayList<>(), Version.getP2PMessageVersion());
|
this(new ArrayList<>(), Version.getP2PMessageVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BundleOfEnvelopes(List<NetworkEnvelope> envelopes) {
|
||||||
|
this(envelopes, Version.getP2PMessageVersion());
|
||||||
|
}
|
||||||
|
|
||||||
public void add(NetworkEnvelope networkEnvelope) {
|
public void add(NetworkEnvelope networkEnvelope) {
|
||||||
envelopes.add(networkEnvelope);
|
envelopes.add(networkEnvelope);
|
||||||
}
|
}
|
||||||
@ -67,7 +72,9 @@ public final class BundleOfEnvelopes extends NetworkEnvelope implements Extended
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BundleOfEnvelopes fromProto(protobuf.BundleOfEnvelopes proto, NetworkProtoResolver resolver, int messageVersion) {
|
public static BundleOfEnvelopes fromProto(protobuf.BundleOfEnvelopes proto,
|
||||||
|
NetworkProtoResolver resolver,
|
||||||
|
int messageVersion) {
|
||||||
List<NetworkEnvelope> envelopes = proto.getEnvelopesList()
|
List<NetworkEnvelope> envelopes = proto.getEnvelopesList()
|
||||||
.stream()
|
.stream()
|
||||||
.map(envelope -> {
|
.map(envelope -> {
|
||||||
|
@ -37,7 +37,6 @@ import bisq.network.p2p.seed.SeedNodeRepository;
|
|||||||
import bisq.network.p2p.storage.HashMapChangedListener;
|
import bisq.network.p2p.storage.HashMapChangedListener;
|
||||||
import bisq.network.p2p.storage.P2PDataStorage;
|
import bisq.network.p2p.storage.P2PDataStorage;
|
||||||
import bisq.network.p2p.storage.messages.AddDataMessage;
|
import bisq.network.p2p.storage.messages.AddDataMessage;
|
||||||
import bisq.network.p2p.storage.messages.BroadcastMessage;
|
|
||||||
import bisq.network.p2p.storage.messages.RefreshOfferMessage;
|
import bisq.network.p2p.storage.messages.RefreshOfferMessage;
|
||||||
import bisq.network.p2p.storage.payload.CapabilityRequiringPayload;
|
import bisq.network.p2p.storage.payload.CapabilityRequiringPayload;
|
||||||
import bisq.network.p2p.storage.payload.MailboxStoragePayload;
|
import bisq.network.p2p.storage.payload.MailboxStoragePayload;
|
||||||
@ -53,7 +52,6 @@ import bisq.common.crypto.PubKeyRing;
|
|||||||
import bisq.common.proto.ProtobufferException;
|
import bisq.common.proto.ProtobufferException;
|
||||||
import bisq.common.proto.network.NetworkEnvelope;
|
import bisq.common.proto.network.NetworkEnvelope;
|
||||||
import bisq.common.proto.persistable.PersistedDataHost;
|
import bisq.common.proto.persistable.PersistedDataHost;
|
||||||
import bisq.common.util.Utilities;
|
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
@ -77,6 +75,7 @@ import java.security.PublicKey;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -122,9 +121,6 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
|
|||||||
private final BooleanProperty preliminaryDataReceived = new SimpleBooleanProperty();
|
private final BooleanProperty preliminaryDataReceived = new SimpleBooleanProperty();
|
||||||
private final IntegerProperty numConnectedPeers = new SimpleIntegerProperty(0);
|
private final IntegerProperty numConnectedPeers = new SimpleIntegerProperty(0);
|
||||||
|
|
||||||
private volatile boolean shutDownInProgress;
|
|
||||||
@Getter
|
|
||||||
private boolean shutDownComplete;
|
|
||||||
private final Subscription networkReadySubscription;
|
private final Subscription networkReadySubscription;
|
||||||
private boolean isBootstrapped;
|
private boolean isBootstrapped;
|
||||||
private final KeepAliveManager keepAliveManager;
|
private final KeepAliveManager keepAliveManager;
|
||||||
@ -212,48 +208,48 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void shutDown(Runnable shutDownCompleteHandler) {
|
public void shutDown(Runnable shutDownCompleteHandler) {
|
||||||
if (!shutDownInProgress) {
|
shutDownResultHandlers.add(shutDownCompleteHandler);
|
||||||
shutDownInProgress = true;
|
|
||||||
|
|
||||||
shutDownResultHandlers.add(shutDownCompleteHandler);
|
// We need to make sure queued up messages are flushed out before we continue shut down other network
|
||||||
|
// services
|
||||||
if (p2PDataStorage != null)
|
if (broadcaster != null) {
|
||||||
p2PDataStorage.shutDown();
|
broadcaster.shutDown(this::doShutDown);
|
||||||
|
|
||||||
if (peerManager != null)
|
|
||||||
peerManager.shutDown();
|
|
||||||
|
|
||||||
if (broadcaster != null)
|
|
||||||
broadcaster.shutDown();
|
|
||||||
|
|
||||||
if (requestDataManager != null)
|
|
||||||
requestDataManager.shutDown();
|
|
||||||
|
|
||||||
if (peerExchangeManager != null)
|
|
||||||
peerExchangeManager.shutDown();
|
|
||||||
|
|
||||||
if (keepAliveManager != null)
|
|
||||||
keepAliveManager.shutDown();
|
|
||||||
|
|
||||||
if (networkReadySubscription != null)
|
|
||||||
networkReadySubscription.unsubscribe();
|
|
||||||
|
|
||||||
if (networkNode != null) {
|
|
||||||
networkNode.shutDown(() -> {
|
|
||||||
shutDownResultHandlers.stream().forEach(Runnable::run);
|
|
||||||
shutDownComplete = true;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
shutDownResultHandlers.stream().forEach(Runnable::run);
|
|
||||||
shutDownComplete = true;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
log.debug("shutDown already in progress");
|
doShutDown();
|
||||||
if (shutDownComplete) {
|
}
|
||||||
shutDownCompleteHandler.run();
|
}
|
||||||
} else {
|
|
||||||
shutDownResultHandlers.add(shutDownCompleteHandler);
|
private void doShutDown() {
|
||||||
}
|
if (p2PDataStorage != null) {
|
||||||
|
p2PDataStorage.shutDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (peerManager != null) {
|
||||||
|
peerManager.shutDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestDataManager != null) {
|
||||||
|
requestDataManager.shutDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (peerExchangeManager != null) {
|
||||||
|
peerExchangeManager.shutDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keepAliveManager != null) {
|
||||||
|
keepAliveManager.shutDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (networkReadySubscription != null) {
|
||||||
|
networkReadySubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (networkNode != null) {
|
||||||
|
networkNode.shutDown(() -> {
|
||||||
|
shutDownResultHandlers.forEach(Runnable::run);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
shutDownResultHandlers.forEach(Runnable::run);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -448,7 +444,7 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRemoved(Collection<ProtectedStorageEntry> protectedStorageEntries) {
|
public void onRemoved(Collection<ProtectedStorageEntry> protectedStorageEntries) {
|
||||||
// not handled
|
// not used
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -672,43 +668,21 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
|
|||||||
|
|
||||||
BroadcastHandler.Listener listener = new BroadcastHandler.Listener() {
|
BroadcastHandler.Listener listener = new BroadcastHandler.Listener() {
|
||||||
@Override
|
@Override
|
||||||
public void onBroadcasted(BroadcastMessage message, int numOfCompletedBroadcasts) {
|
public void onSufficientlyBroadcast(List<Broadcaster.BroadcastRequest> broadcastRequests) {
|
||||||
|
broadcastRequests.stream()
|
||||||
|
.filter(broadcastRequest -> broadcastRequest.getMessage() instanceof AddDataMessage)
|
||||||
|
.filter(broadcastRequest -> {
|
||||||
|
AddDataMessage addDataMessage = (AddDataMessage) broadcastRequest.getMessage();
|
||||||
|
return addDataMessage.getProtectedStorageEntry().equals(protectedMailboxStorageEntry);
|
||||||
|
})
|
||||||
|
.forEach(e -> sendMailboxMessageListener.onStoredInMailbox());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBroadcastedToFirstPeer(BroadcastMessage message) {
|
public void onNotSufficientlyBroadcast(int numOfCompletedBroadcasts, int numOfFailedBroadcast) {
|
||||||
// The reason for that check was to separate different callback for different send calls.
|
sendMailboxMessageListener.onFault("Message was not sufficiently broadcast.\n" +
|
||||||
// We only want to notify our sendMailboxMessageListener for the calls he is interested in.
|
"numOfCompletedBroadcasts: " + numOfCompletedBroadcasts + ".\n" +
|
||||||
if (message instanceof AddDataMessage &&
|
"numOfFailedBroadcast=" + numOfFailedBroadcast);
|
||||||
((AddDataMessage) message).getProtectedStorageEntry().equals(protectedMailboxStorageEntry)) {
|
|
||||||
// We delay a bit to give more time for sufficient propagation in the P2P network.
|
|
||||||
// This should help to avoid situations where a user closes the app too early and the msg
|
|
||||||
// does not arrive.
|
|
||||||
// We could use onBroadcastCompleted instead but it might take too long if one peer
|
|
||||||
// is very badly connected.
|
|
||||||
// TODO We could check for a certain threshold of no. of incoming network_messages of the same msg
|
|
||||||
// to see how well it is propagated. BitcoinJ uses such an approach for tx propagation.
|
|
||||||
UserThread.runAfter(() -> {
|
|
||||||
log.info("Broadcasted to first peer (3 sec. ago): Message = {}", Utilities.toTruncatedString(message));
|
|
||||||
sendMailboxMessageListener.onStoredInMailbox();
|
|
||||||
}, 3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBroadcastCompleted(BroadcastMessage message,
|
|
||||||
int numOfCompletedBroadcasts,
|
|
||||||
int numOfFailedBroadcasts) {
|
|
||||||
log.info("Broadcast completed: Sent to {} peers (failed: {}). Message = {}",
|
|
||||||
numOfCompletedBroadcasts, numOfFailedBroadcasts, Utilities.toTruncatedString(message));
|
|
||||||
if (numOfCompletedBroadcasts == 0)
|
|
||||||
sendMailboxMessageListener.onFault("Broadcast completed without any successful broadcast");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBroadcastFailed(String errorMessage) {
|
|
||||||
// TODO investigate why not sending sendMailboxMessageListener.onFault. Related probably
|
|
||||||
// to the logic from BroadcastHandler.sendToPeer
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
boolean result = p2PDataStorage.addProtectedStorageEntry(protectedMailboxStorageEntry, networkNode.getNodeAddress(), listener);
|
boolean result = p2PDataStorage.addProtectedStorageEntry(protectedMailboxStorageEntry, networkNode.getNodeAddress(), listener);
|
||||||
@ -721,7 +695,7 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
|
|||||||
log.error("Unexpected state: adding mailbox message that already exists.");
|
log.error("Unexpected state: adding mailbox message that already exists.");
|
||||||
}
|
}
|
||||||
} catch (CryptoException e) {
|
} catch (CryptoException e) {
|
||||||
log.error("Signing at getDataWithSignedSeqNr failed. That should never happen.");
|
log.error("Signing at getMailboxDataWithSignedSeqNr failed.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sendMailboxMessageListener.onFault("There are no P2P network nodes connected. " +
|
sendMailboxMessageListener.onFault("There are no P2P network nodes connected. " +
|
||||||
|
@ -225,7 +225,7 @@ public class Connection implements HasCapabilities, Runnable, MessageListener {
|
|||||||
|
|
||||||
// Called from various threads
|
// Called from various threads
|
||||||
public void sendMessage(NetworkEnvelope networkEnvelope) {
|
public void sendMessage(NetworkEnvelope networkEnvelope) {
|
||||||
log.debug(">> Send networkEnvelope of type: " + networkEnvelope.getClass().getSimpleName());
|
log.debug(">> Send networkEnvelope of type: {}", networkEnvelope.getClass().getSimpleName());
|
||||||
|
|
||||||
if (!stopped) {
|
if (!stopped) {
|
||||||
if (noCapabilityRequiredOrCapabilityIsSupported(networkEnvelope)) {
|
if (noCapabilityRequiredOrCapabilityIsSupported(networkEnvelope)) {
|
||||||
@ -319,6 +319,8 @@ public class Connection implements HasCapabilities, Runnable, MessageListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: If msg is BundleOfEnvelopes we should check each individual message for capability and filter out those
|
||||||
|
// which fail.
|
||||||
public boolean noCapabilityRequiredOrCapabilityIsSupported(Proto msg) {
|
public boolean noCapabilityRequiredOrCapabilityIsSupported(Proto msg) {
|
||||||
boolean result;
|
boolean result;
|
||||||
if (msg instanceof AddDataMessage) {
|
if (msg instanceof AddDataMessage) {
|
||||||
@ -408,12 +410,13 @@ public class Connection implements HasCapabilities, Runnable, MessageListener {
|
|||||||
public void onMessage(NetworkEnvelope networkEnvelope, Connection connection) {
|
public void onMessage(NetworkEnvelope networkEnvelope, Connection connection) {
|
||||||
checkArgument(connection.equals(this));
|
checkArgument(connection.equals(this));
|
||||||
|
|
||||||
if (networkEnvelope instanceof BundleOfEnvelopes)
|
if (networkEnvelope instanceof BundleOfEnvelopes) {
|
||||||
for (NetworkEnvelope current : ((BundleOfEnvelopes) networkEnvelope).getEnvelopes()) {
|
for (NetworkEnvelope current : ((BundleOfEnvelopes) networkEnvelope).getEnvelopes()) {
|
||||||
UserThread.execute(() -> messageListeners.forEach(e -> e.onMessage(current, connection)));
|
UserThread.execute(() -> messageListeners.forEach(e -> e.onMessage(current, connection)));
|
||||||
}
|
}
|
||||||
else
|
} else {
|
||||||
UserThread.execute(() -> messageListeners.forEach(e -> e.onMessage(networkEnvelope, connection)));
|
UserThread.execute(() -> messageListeners.forEach(e -> e.onMessage(networkEnvelope, connection)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package bisq.network.p2p.peers;
|
package bisq.network.p2p.peers;
|
||||||
|
|
||||||
|
import bisq.network.p2p.BundleOfEnvelopes;
|
||||||
import bisq.network.p2p.NodeAddress;
|
import bisq.network.p2p.NodeAddress;
|
||||||
import bisq.network.p2p.network.Connection;
|
import bisq.network.p2p.network.Connection;
|
||||||
import bisq.network.p2p.network.NetworkNode;
|
import bisq.network.p2p.network.NetworkNode;
|
||||||
@ -24,7 +25,6 @@ import bisq.network.p2p.storage.messages.BroadcastMessage;
|
|||||||
|
|
||||||
import bisq.common.Timer;
|
import bisq.common.Timer;
|
||||||
import bisq.common.UserThread;
|
import bisq.common.UserThread;
|
||||||
import bisq.common.util.Utilities;
|
|
||||||
|
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
@ -33,7 +33,6 @@ import com.google.common.util.concurrent.SettableFuture;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -41,11 +40,10 @@ import java.util.stream.Collectors;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class BroadcastHandler implements PeerManager.Listener {
|
public class BroadcastHandler implements PeerManager.Listener {
|
||||||
private static final long TIMEOUT = 60;
|
private static final long BASE_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(60);
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -54,20 +52,12 @@ public class BroadcastHandler implements PeerManager.Listener {
|
|||||||
|
|
||||||
interface ResultHandler {
|
interface ResultHandler {
|
||||||
void onCompleted(BroadcastHandler broadcastHandler);
|
void onCompleted(BroadcastHandler broadcastHandler);
|
||||||
|
|
||||||
void onFault(BroadcastHandler broadcastHandler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface Listener {
|
public interface Listener {
|
||||||
@SuppressWarnings({"EmptyMethod", "UnusedParameters"})
|
void onSufficientlyBroadcast(List<Broadcaster.BroadcastRequest> broadcastRequests);
|
||||||
void onBroadcasted(BroadcastMessage message, int numOfCompletedBroadcasts);
|
|
||||||
|
|
||||||
void onBroadcastedToFirstPeer(BroadcastMessage message);
|
void onNotSufficientlyBroadcast(int numOfCompletedBroadcasts, int numOfFailedBroadcast);
|
||||||
|
|
||||||
void onBroadcastCompleted(BroadcastMessage message, int numOfCompletedBroadcasts, int numOfFailedBroadcasts);
|
|
||||||
|
|
||||||
@SuppressWarnings({"EmptyMethod", "UnusedParameters"})
|
|
||||||
void onBroadcastFailed(String errorMessage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -76,16 +66,12 @@ public class BroadcastHandler implements PeerManager.Listener {
|
|||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
private final NetworkNode networkNode;
|
private final NetworkNode networkNode;
|
||||||
public final String uid;
|
|
||||||
private final PeerManager peerManager;
|
private final PeerManager peerManager;
|
||||||
private boolean stopped = false;
|
private final ResultHandler resultHandler;
|
||||||
private int numOfCompletedBroadcasts = 0;
|
private final String uid;
|
||||||
private int numOfFailedBroadcasts = 0;
|
|
||||||
private BroadcastMessage message;
|
private boolean stopped, timeoutTriggered;
|
||||||
private ResultHandler resultHandler;
|
private int numOfCompletedBroadcasts, numOfFailedBroadcasts, numPeersForBroadcast;
|
||||||
@Nullable
|
|
||||||
private Listener listener;
|
|
||||||
private int numPeers;
|
|
||||||
private Timer timeoutTimer;
|
private Timer timeoutTimer;
|
||||||
|
|
||||||
|
|
||||||
@ -93,16 +79,13 @@ public class BroadcastHandler implements PeerManager.Listener {
|
|||||||
// Constructor
|
// Constructor
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public BroadcastHandler(NetworkNode networkNode, PeerManager peerManager) {
|
BroadcastHandler(NetworkNode networkNode, PeerManager peerManager, ResultHandler resultHandler) {
|
||||||
this.networkNode = networkNode;
|
this.networkNode = networkNode;
|
||||||
this.peerManager = peerManager;
|
this.peerManager = peerManager;
|
||||||
peerManager.addListener(this);
|
this.resultHandler = resultHandler;
|
||||||
uid = UUID.randomUUID().toString();
|
uid = UUID.randomUUID().toString();
|
||||||
}
|
|
||||||
|
|
||||||
public void cancel() {
|
peerManager.addListener(this);
|
||||||
stopped = true;
|
|
||||||
onFault("Broadcast canceled.", false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -110,110 +93,73 @@ public class BroadcastHandler implements PeerManager.Listener {
|
|||||||
// API
|
// API
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public void broadcast(BroadcastMessage message, @Nullable NodeAddress sender, ResultHandler resultHandler,
|
public void broadcast(List<Broadcaster.BroadcastRequest> broadcastRequests, boolean shutDownRequested) {
|
||||||
@Nullable Listener listener) {
|
List<Connection> confirmedConnections = new ArrayList<>(networkNode.getConfirmedConnections());
|
||||||
this.message = message;
|
Collections.shuffle(confirmedConnections);
|
||||||
this.resultHandler = resultHandler;
|
|
||||||
this.listener = listener;
|
|
||||||
|
|
||||||
Set<Connection> connectedPeersSet = networkNode.getConfirmedConnections()
|
int delay;
|
||||||
.stream()
|
if (shutDownRequested) {
|
||||||
.filter(connection -> !connection.getPeersNodeAddressOptional().get().equals(sender))
|
delay = 1;
|
||||||
.collect(Collectors.toSet());
|
// We sent to all peers as in case we had offers we want that it gets removed with higher reliability
|
||||||
if (!connectedPeersSet.isEmpty()) {
|
numPeersForBroadcast = confirmedConnections.size();
|
||||||
numOfCompletedBroadcasts = 0;
|
} else {
|
||||||
|
if (requestsContainOwnMessage(broadcastRequests)) {
|
||||||
List<Connection> connectedPeersList = new ArrayList<>(connectedPeersSet);
|
// The broadcastRequests contains at least 1 message we have originated, so we send to all peers and
|
||||||
Collections.shuffle(connectedPeersList);
|
// with shorter delay
|
||||||
numPeers = connectedPeersList.size();
|
numPeersForBroadcast = confirmedConnections.size();
|
||||||
int delay = 50;
|
delay = 50;
|
||||||
|
} else {
|
||||||
boolean isDataOwner = (sender != null) && sender.equals(networkNode.getNodeAddress());
|
// Relay nodes only send to max 7 peers and with longer delay
|
||||||
if (!isDataOwner) {
|
numPeersForBroadcast = Math.min(7, confirmedConnections.size());
|
||||||
// for not data owner (relay nodes) we send to max. 7 nodes and use a longer delay
|
|
||||||
numPeers = Math.min(7, connectedPeersList.size());
|
|
||||||
delay = 100;
|
delay = 100;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
long timeoutDelay = TIMEOUT + delay * numPeers;
|
setupTimeoutHandler(broadcastRequests, delay, shutDownRequested);
|
||||||
timeoutTimer = UserThread.runAfter(() -> { // setup before sending to avoid race conditions
|
|
||||||
String errorMessage = "Timeout: Broadcast did not complete after " + timeoutDelay + " sec.";
|
|
||||||
|
|
||||||
log.debug(errorMessage + "\n\t" +
|
int iterations = numPeersForBroadcast;
|
||||||
"numOfPeers=" + numPeers + "\n\t" +
|
for (int i = 0; i < iterations; i++) {
|
||||||
"numOfCompletedBroadcasts=" + numOfCompletedBroadcasts + "\n\t" +
|
long minDelay = (i + 1) * delay;
|
||||||
"numOfFailedBroadcasts=" + numOfFailedBroadcasts);
|
long maxDelay = (i + 2) * delay;
|
||||||
onFault(errorMessage, false);
|
Connection connection = confirmedConnections.get(i);
|
||||||
}, timeoutDelay);
|
UserThread.runAfterRandomDelay(() -> {
|
||||||
|
if (stopped) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
log.debug("Broadcast message to {} peers out of {} total connected peers.", numPeers, connectedPeersSet.size());
|
// We use broadcastRequests which have excluded the requests for messages the connection has
|
||||||
for (int i = 0; i < numPeers; i++) {
|
// originated to avoid sending back the message we received. We also remove messages not satisfying
|
||||||
if (stopped)
|
// capability checks.
|
||||||
break; // do not continue sending after a timeout or a cancellation
|
List<Broadcaster.BroadcastRequest> broadcastRequestsForConnection = getBroadcastRequestsForConnection(connection, broadcastRequests);
|
||||||
|
|
||||||
final long minDelay = (i + 1) * delay;
|
// Could be empty list...
|
||||||
final long maxDelay = (i + 2) * delay;
|
if (broadcastRequestsForConnection.isEmpty()) {
|
||||||
final Connection connection = connectedPeersList.get(i);
|
// We decrease numPeers in that case for making completion checks correct.
|
||||||
UserThread.runAfterRandomDelay(() -> sendToPeer(connection, message), minDelay, maxDelay, TimeUnit.MILLISECONDS);
|
if (numPeersForBroadcast > 0) {
|
||||||
}
|
numPeersForBroadcast--;
|
||||||
} else {
|
}
|
||||||
onFault("Message not broadcasted because we have no available peers yet.\n\t" +
|
checkForCompletion();
|
||||||
"message = " + Utilities.toTruncatedString(message), false);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connection.isStopped()) {
|
||||||
|
// Connection has died in the meantime. We skip it.
|
||||||
|
// We decrease numPeers in that case for making completion checks correct.
|
||||||
|
if (numPeersForBroadcast > 0) {
|
||||||
|
numPeersForBroadcast--;
|
||||||
|
}
|
||||||
|
checkForCompletion();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendToPeer(connection, broadcastRequestsForConnection);
|
||||||
|
}, minDelay, maxDelay, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendToPeer(Connection connection, BroadcastMessage message) {
|
public void cancel() {
|
||||||
String errorMessage = "Message not broadcasted because we have stopped the handler already.\n\t" +
|
stopped = true;
|
||||||
"message = " + Utilities.toTruncatedString(message);
|
cleanup();
|
||||||
if (!stopped) {
|
|
||||||
if (!connection.isStopped()) {
|
|
||||||
if (connection.noCapabilityRequiredOrCapabilityIsSupported(message)) {
|
|
||||||
NodeAddress nodeAddress = connection.getPeersNodeAddressOptional().get();
|
|
||||||
SettableFuture<Connection> future = networkNode.sendMessage(connection, message);
|
|
||||||
Futures.addCallback(future, new FutureCallback<Connection>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Connection connection) {
|
|
||||||
numOfCompletedBroadcasts++;
|
|
||||||
if (!stopped) {
|
|
||||||
if (listener != null)
|
|
||||||
listener.onBroadcasted(message, numOfCompletedBroadcasts);
|
|
||||||
|
|
||||||
if (listener != null && numOfCompletedBroadcasts == 1)
|
|
||||||
listener.onBroadcastedToFirstPeer(message);
|
|
||||||
|
|
||||||
if (numOfCompletedBroadcasts + numOfFailedBroadcasts == numPeers) {
|
|
||||||
if (listener != null)
|
|
||||||
listener.onBroadcastCompleted(message, numOfCompletedBroadcasts, numOfFailedBroadcasts);
|
|
||||||
|
|
||||||
cleanup();
|
|
||||||
resultHandler.onCompleted(BroadcastHandler.this);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// TODO investigate why that is called very often at seed nodes
|
|
||||||
onFault("stopped at onSuccess: " + errorMessage, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NotNull Throwable throwable) {
|
|
||||||
numOfFailedBroadcasts++;
|
|
||||||
if (!stopped) {
|
|
||||||
log.info("Broadcast to " + nodeAddress + " failed.\n\t" +
|
|
||||||
"ErrorMessage=" + throwable.getMessage());
|
|
||||||
if (numOfCompletedBroadcasts + numOfFailedBroadcasts == numPeers)
|
|
||||||
onFault("stopped at onFailure: " + errorMessage);
|
|
||||||
} else {
|
|
||||||
onFault("stopped at onFailure: " + errorMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
onFault("Connection stopped already", false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
onFault("stopped at sendToPeer: " + errorMessage, false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -223,7 +169,7 @@ public class BroadcastHandler implements PeerManager.Listener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAllConnectionsLost() {
|
public void onAllConnectionsLost() {
|
||||||
onFault("All connections lost", false);
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -239,37 +185,142 @@ public class BroadcastHandler implements PeerManager.Listener {
|
|||||||
// Private
|
// Private
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Check if we have at least one message originated by ourselves
|
||||||
|
private boolean requestsContainOwnMessage(List<Broadcaster.BroadcastRequest> broadcastRequests) {
|
||||||
|
NodeAddress myAddress = networkNode.getNodeAddress();
|
||||||
|
if (myAddress == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return broadcastRequests.stream().anyMatch(e -> myAddress.equals(e.getSender()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupTimeoutHandler(List<Broadcaster.BroadcastRequest> broadcastRequests,
|
||||||
|
int delay,
|
||||||
|
boolean shutDownRequested) {
|
||||||
|
// In case of shutdown we try to complete fast and set a short 1 second timeout
|
||||||
|
long baseTimeoutMs = shutDownRequested ? TimeUnit.SECONDS.toMillis(1) : BASE_TIMEOUT_MS;
|
||||||
|
long timeoutDelay = baseTimeoutMs + delay * (numPeersForBroadcast + 1); // We added 1 in the loop
|
||||||
|
timeoutTimer = UserThread.runAfter(() -> {
|
||||||
|
if (stopped) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutTriggered = true;
|
||||||
|
|
||||||
|
log.warn("Broadcast did not complete after {} sec.\n" +
|
||||||
|
"numPeersForBroadcast={}\n" +
|
||||||
|
"numOfCompletedBroadcasts={}\n" +
|
||||||
|
"numOfFailedBroadcasts={}",
|
||||||
|
timeoutDelay / 1000d,
|
||||||
|
numPeersForBroadcast,
|
||||||
|
numOfCompletedBroadcasts,
|
||||||
|
numOfFailedBroadcasts);
|
||||||
|
|
||||||
|
maybeNotifyListeners(broadcastRequests);
|
||||||
|
|
||||||
|
cleanup();
|
||||||
|
|
||||||
|
}, timeoutDelay, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We exclude the requests containing a message we received from that connection
|
||||||
|
// Also we filter out messages which requires a capability but peer does not support it.
|
||||||
|
private List<Broadcaster.BroadcastRequest> getBroadcastRequestsForConnection(Connection connection,
|
||||||
|
List<Broadcaster.BroadcastRequest> broadcastRequests) {
|
||||||
|
return broadcastRequests.stream()
|
||||||
|
.filter(broadcastRequest -> !connection.getPeersNodeAddressOptional().isPresent() ||
|
||||||
|
!connection.getPeersNodeAddressOptional().get().equals(broadcastRequest.getSender()))
|
||||||
|
.filter(broadcastRequest -> connection.noCapabilityRequiredOrCapabilityIsSupported(broadcastRequest.getMessage()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendToPeer(Connection connection, List<Broadcaster.BroadcastRequest> broadcastRequestsForConnection) {
|
||||||
|
// Can be BundleOfEnvelopes or a single BroadcastMessage
|
||||||
|
BroadcastMessage broadcastMessage = getMessage(broadcastRequestsForConnection);
|
||||||
|
SettableFuture<Connection> future = networkNode.sendMessage(connection, broadcastMessage);
|
||||||
|
|
||||||
|
Futures.addCallback(future, new FutureCallback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Connection connection) {
|
||||||
|
numOfCompletedBroadcasts++;
|
||||||
|
|
||||||
|
if (stopped) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
maybeNotifyListeners(broadcastRequestsForConnection);
|
||||||
|
checkForCompletion();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NotNull Throwable throwable) {
|
||||||
|
log.warn("Broadcast to {} failed. ErrorMessage={}", connection.getPeersNodeAddressOptional(),
|
||||||
|
throwable.getMessage());
|
||||||
|
numOfFailedBroadcasts++;
|
||||||
|
|
||||||
|
if (stopped) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
maybeNotifyListeners(broadcastRequestsForConnection);
|
||||||
|
checkForCompletion();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private BroadcastMessage getMessage(List<Broadcaster.BroadcastRequest> broadcastRequests) {
|
||||||
|
if (broadcastRequests.size() == 1) {
|
||||||
|
// If we only have 1 message we avoid the overhead of the BundleOfEnvelopes and send the message directly
|
||||||
|
return broadcastRequests.get(0).getMessage();
|
||||||
|
} else {
|
||||||
|
return new BundleOfEnvelopes(broadcastRequests.stream()
|
||||||
|
.map(Broadcaster.BroadcastRequest::getMessage)
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeNotifyListeners(List<Broadcaster.BroadcastRequest> broadcastRequests) {
|
||||||
|
int numOfCompletedBroadcastsTarget = Math.max(1, Math.min(numPeersForBroadcast, 3));
|
||||||
|
// We use equal checks to avoid duplicated listener calls as it would be the case with >= checks.
|
||||||
|
if (numOfCompletedBroadcasts == numOfCompletedBroadcastsTarget) {
|
||||||
|
// We have heard back from 3 peers (or all peers if numPeers is lower) so we consider the message was sufficiently broadcast.
|
||||||
|
broadcastRequests.stream()
|
||||||
|
.filter(broadcastRequest -> broadcastRequest.getListener() != null)
|
||||||
|
.map(Broadcaster.BroadcastRequest::getListener)
|
||||||
|
.forEach(listener -> listener.onSufficientlyBroadcast(broadcastRequests));
|
||||||
|
} else {
|
||||||
|
// We check if number of open requests to peers is less than we need to reach numOfCompletedBroadcastsTarget.
|
||||||
|
// Thus we never can reach required resilience as too many numOfFailedBroadcasts occurred.
|
||||||
|
int maxPossibleSuccessCases = numPeersForBroadcast - numOfFailedBroadcasts;
|
||||||
|
// We subtract 1 as we want to have it called only once, with a < comparision we would trigger repeatedly.
|
||||||
|
boolean notEnoughSucceededOrOpen = maxPossibleSuccessCases == numOfCompletedBroadcastsTarget - 1;
|
||||||
|
// We did not reach resilience level and timeout prevents to reach it later
|
||||||
|
boolean timeoutAndNotEnoughSucceeded = timeoutTriggered && numOfCompletedBroadcasts < numOfCompletedBroadcastsTarget;
|
||||||
|
if (notEnoughSucceededOrOpen || timeoutAndNotEnoughSucceeded) {
|
||||||
|
broadcastRequests.stream()
|
||||||
|
.filter(broadcastRequest -> broadcastRequest.getListener() != null)
|
||||||
|
.map(Broadcaster.BroadcastRequest::getListener)
|
||||||
|
.forEach(listener -> listener.onNotSufficientlyBroadcast(numOfCompletedBroadcasts, numOfFailedBroadcasts));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkForCompletion() {
|
||||||
|
if (numOfCompletedBroadcasts + numOfFailedBroadcasts == numPeersForBroadcast) {
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void cleanup() {
|
private void cleanup() {
|
||||||
stopped = true;
|
stopped = true;
|
||||||
peerManager.removeListener(this);
|
|
||||||
if (timeoutTimer != null) {
|
if (timeoutTimer != null) {
|
||||||
timeoutTimer.stop();
|
timeoutTimer.stop();
|
||||||
timeoutTimer = null;
|
timeoutTimer = null;
|
||||||
}
|
}
|
||||||
|
peerManager.removeListener(this);
|
||||||
|
resultHandler.onCompleted(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onFault(String errorMessage) {
|
|
||||||
onFault(errorMessage, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onFault(String errorMessage, boolean logWarning) {
|
|
||||||
cleanup();
|
|
||||||
|
|
||||||
if (logWarning)
|
|
||||||
log.warn(errorMessage);
|
|
||||||
else
|
|
||||||
log.debug(errorMessage);
|
|
||||||
|
|
||||||
if (listener != null)
|
|
||||||
listener.onBroadcastFailed(errorMessage);
|
|
||||||
|
|
||||||
if (listener != null && (numOfCompletedBroadcasts + numOfFailedBroadcasts == numPeers || stopped))
|
|
||||||
listener.onBroadcastCompleted(message, numOfCompletedBroadcasts, numOfFailedBroadcasts);
|
|
||||||
|
|
||||||
resultHandler.onFault(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
@ -277,11 +328,11 @@ public class BroadcastHandler implements PeerManager.Listener {
|
|||||||
|
|
||||||
BroadcastHandler that = (BroadcastHandler) o;
|
BroadcastHandler that = (BroadcastHandler) o;
|
||||||
|
|
||||||
return !(uid != null ? !uid.equals(that.uid) : that.uid != null);
|
return uid.equals(that.uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return uid != null ? uid.hashCode() : 0;
|
return uid.hashCode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,18 +21,34 @@ import bisq.network.p2p.NodeAddress;
|
|||||||
import bisq.network.p2p.network.NetworkNode;
|
import bisq.network.p2p.network.NetworkNode;
|
||||||
import bisq.network.p2p.storage.messages.BroadcastMessage;
|
import bisq.network.p2p.storage.messages.BroadcastMessage;
|
||||||
|
|
||||||
|
import bisq.common.Timer;
|
||||||
|
import bisq.common.UserThread;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import lombok.Value;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class Broadcaster implements BroadcastHandler.ResultHandler {
|
public class Broadcaster implements BroadcastHandler.ResultHandler {
|
||||||
|
private static final long BROADCAST_INTERVAL_MS = 2000;
|
||||||
|
|
||||||
private final NetworkNode networkNode;
|
private final NetworkNode networkNode;
|
||||||
private final PeerManager peerManager;
|
private final PeerManager peerManager;
|
||||||
|
|
||||||
private final Set<BroadcastHandler> broadcastHandlers = new CopyOnWriteArraySet<>();
|
private final Set<BroadcastHandler> broadcastHandlers = new CopyOnWriteArraySet<>();
|
||||||
|
private final List<BroadcastRequest> broadcastRequests = new ArrayList<>();
|
||||||
|
private Timer timer;
|
||||||
|
private boolean shutDownRequested;
|
||||||
|
private Runnable shutDownResultHandler;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -45,9 +61,24 @@ public class Broadcaster implements BroadcastHandler.ResultHandler {
|
|||||||
this.peerManager = peerManager;
|
this.peerManager = peerManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void shutDown() {
|
public void shutDown(Runnable resultHandler) {
|
||||||
broadcastHandlers.stream().forEach(BroadcastHandler::cancel);
|
shutDownRequested = true;
|
||||||
broadcastHandlers.clear();
|
shutDownResultHandler = resultHandler;
|
||||||
|
if (broadcastRequests.isEmpty()) {
|
||||||
|
doShutDown();
|
||||||
|
} else {
|
||||||
|
// We set delay of broadcasts and timeout to very low values,
|
||||||
|
// so we can expect that we get onCompleted called very fast and trigger the doShutDown from there.
|
||||||
|
maybeBroadcastBundle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doShutDown() {
|
||||||
|
broadcastHandlers.forEach(BroadcastHandler::cancel);
|
||||||
|
if (timer != null) {
|
||||||
|
timer.stop();
|
||||||
|
}
|
||||||
|
shutDownResultHandler.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -55,11 +86,38 @@ public class Broadcaster implements BroadcastHandler.ResultHandler {
|
|||||||
// API
|
// API
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public void broadcast(BroadcastMessage message, @Nullable NodeAddress sender,
|
public void broadcast(BroadcastMessage message,
|
||||||
|
@Nullable NodeAddress sender) {
|
||||||
|
broadcast(message, sender, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void broadcast(BroadcastMessage message,
|
||||||
|
@Nullable NodeAddress sender,
|
||||||
@Nullable BroadcastHandler.Listener listener) {
|
@Nullable BroadcastHandler.Listener listener) {
|
||||||
BroadcastHandler broadcastHandler = new BroadcastHandler(networkNode, peerManager);
|
broadcastRequests.add(new BroadcastRequest(message, sender, listener));
|
||||||
broadcastHandler.broadcast(message, sender, this, listener);
|
// Keep that log on INFO for better debugging if the feature works as expected. Later it can
|
||||||
broadcastHandlers.add(broadcastHandler);
|
// be remove or set to DEBUG
|
||||||
|
log.debug("Broadcast requested for {}. We queue it up for next bundled broadcast.",
|
||||||
|
message.getClass().getSimpleName());
|
||||||
|
|
||||||
|
if (timer == null) {
|
||||||
|
timer = UserThread.runAfter(this::maybeBroadcastBundle, BROADCAST_INTERVAL_MS, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeBroadcastBundle() {
|
||||||
|
if (!broadcastRequests.isEmpty()) {
|
||||||
|
log.debug("Broadcast bundled requests of {} messages. Message types: {}",
|
||||||
|
broadcastRequests.size(),
|
||||||
|
broadcastRequests.stream().map(e -> e.getMessage().getClass().getSimpleName()).collect(Collectors.toList()));
|
||||||
|
BroadcastHandler broadcastHandler = new BroadcastHandler(networkNode, peerManager, this);
|
||||||
|
broadcastHandlers.add(broadcastHandler);
|
||||||
|
broadcastHandler.broadcast(new ArrayList<>(broadcastRequests), shutDownRequested);
|
||||||
|
broadcastRequests.clear();
|
||||||
|
|
||||||
|
timer = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -70,10 +128,30 @@ public class Broadcaster implements BroadcastHandler.ResultHandler {
|
|||||||
@Override
|
@Override
|
||||||
public void onCompleted(BroadcastHandler broadcastHandler) {
|
public void onCompleted(BroadcastHandler broadcastHandler) {
|
||||||
broadcastHandlers.remove(broadcastHandler);
|
broadcastHandlers.remove(broadcastHandler);
|
||||||
|
if (shutDownRequested) {
|
||||||
|
doShutDown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFault(BroadcastHandler broadcastHandler) {
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
broadcastHandlers.remove(broadcastHandler);
|
// BroadcastRequest class
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Value
|
||||||
|
public static class BroadcastRequest {
|
||||||
|
private BroadcastMessage message;
|
||||||
|
@Nullable
|
||||||
|
private NodeAddress sender;
|
||||||
|
@Nullable
|
||||||
|
private BroadcastHandler.Listener listener;
|
||||||
|
|
||||||
|
private BroadcastRequest(BroadcastMessage message,
|
||||||
|
@Nullable NodeAddress sender,
|
||||||
|
@Nullable BroadcastHandler.Listener listener) {
|
||||||
|
this.message = message;
|
||||||
|
this.sender = sender;
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,8 +49,8 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
public class KeepAliveManager implements MessageListener, ConnectionListener, PeerManager.Listener {
|
public class KeepAliveManager implements MessageListener, ConnectionListener, PeerManager.Listener {
|
||||||
private static final Logger log = LoggerFactory.getLogger(KeepAliveManager.class);
|
private static final Logger log = LoggerFactory.getLogger(KeepAliveManager.class);
|
||||||
|
|
||||||
private static final int INTERVAL_SEC = new Random().nextInt(5) + 30;
|
private static final int INTERVAL_SEC = new Random().nextInt(30) + 30;
|
||||||
private static final long LAST_ACTIVITY_AGE_MS = INTERVAL_SEC / 2;
|
private static final long LAST_ACTIVITY_AGE_MS = INTERVAL_SEC * 1000 / 2;
|
||||||
|
|
||||||
private final NetworkNode networkNode;
|
private final NetworkNode networkNode;
|
||||||
private final PeerManager peerManager;
|
private final PeerManager peerManager;
|
||||||
|
@ -529,7 +529,7 @@ public class P2PDataStorage implements MessageListener, ConnectionListener, Pers
|
|||||||
|
|
||||||
// Broadcast the payload if requested by caller
|
// Broadcast the payload if requested by caller
|
||||||
if (allowBroadcast)
|
if (allowBroadcast)
|
||||||
broadcaster.broadcast(new AddPersistableNetworkPayloadMessage(payload), sender, null);
|
broadcaster.broadcast(new AddPersistableNetworkPayloadMessage(payload), sender);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -675,7 +675,7 @@ public class P2PDataStorage implements MessageListener, ConnectionListener, Pers
|
|||||||
sequenceNumberMapStorage.queueUpForSave(SequenceNumberMap.clone(sequenceNumberMap), 1000);
|
sequenceNumberMapStorage.queueUpForSave(SequenceNumberMap.clone(sequenceNumberMap), 1000);
|
||||||
|
|
||||||
// Always broadcast refreshes
|
// Always broadcast refreshes
|
||||||
broadcaster.broadcast(refreshTTLMessage, sender, null);
|
broadcaster.broadcast(refreshTTLMessage, sender);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -725,9 +725,9 @@ public class P2PDataStorage implements MessageListener, ConnectionListener, Pers
|
|||||||
printData("after remove");
|
printData("after remove");
|
||||||
|
|
||||||
if (protectedStorageEntry instanceof ProtectedMailboxStorageEntry) {
|
if (protectedStorageEntry instanceof ProtectedMailboxStorageEntry) {
|
||||||
broadcaster.broadcast(new RemoveMailboxDataMessage((ProtectedMailboxStorageEntry) protectedStorageEntry), sender, null);
|
broadcaster.broadcast(new RemoveMailboxDataMessage((ProtectedMailboxStorageEntry) protectedStorageEntry), sender);
|
||||||
} else {
|
} else {
|
||||||
broadcaster.broadcast(new RemoveDataMessage(protectedStorageEntry), sender, null);
|
broadcaster.broadcast(new RemoveDataMessage(protectedStorageEntry), sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -33,8 +33,6 @@ import org.junit.Before;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
@ -69,7 +67,7 @@ public class P2PDataStorageOnMessageHandlerTest {
|
|||||||
this.testState.mockedStorage.onMessage(envelope, mockedConnection);
|
this.testState.mockedStorage.onMessage(envelope, mockedConnection);
|
||||||
|
|
||||||
verify(this.testState.appendOnlyDataStoreListener, never()).onAdded(any(PersistableNetworkPayload.class));
|
verify(this.testState.appendOnlyDataStoreListener, never()).onAdded(any(PersistableNetworkPayload.class));
|
||||||
verify(this.testState.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), any(NodeAddress.class), eq(null));
|
verify(this.testState.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), any(NodeAddress.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -82,7 +80,7 @@ public class P2PDataStorageOnMessageHandlerTest {
|
|||||||
this.testState.mockedStorage.onMessage(envelope, mockedConnection);
|
this.testState.mockedStorage.onMessage(envelope, mockedConnection);
|
||||||
|
|
||||||
verify(this.testState.appendOnlyDataStoreListener, never()).onAdded(any(PersistableNetworkPayload.class));
|
verify(this.testState.appendOnlyDataStoreListener, never()).onAdded(any(PersistableNetworkPayload.class));
|
||||||
verify(this.testState.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), any(NodeAddress.class), eq(null));
|
verify(this.testState.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), any(NodeAddress.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -96,6 +94,6 @@ public class P2PDataStorageOnMessageHandlerTest {
|
|||||||
this.testState.mockedStorage.onMessage(envelope, mockedConnection);
|
this.testState.mockedStorage.onMessage(envelope, mockedConnection);
|
||||||
|
|
||||||
verify(this.testState.appendOnlyDataStoreListener, never()).onAdded(any(PersistableNetworkPayload.class));
|
verify(this.testState.appendOnlyDataStoreListener, never()).onAdded(any(PersistableNetworkPayload.class));
|
||||||
verify(this.testState.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), any(NodeAddress.class), eq(null));
|
verify(this.testState.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), any(NodeAddress.class));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@ package bisq.network.p2p.storage;
|
|||||||
|
|
||||||
import bisq.network.p2p.NodeAddress;
|
import bisq.network.p2p.NodeAddress;
|
||||||
import bisq.network.p2p.network.NetworkNode;
|
import bisq.network.p2p.network.NetworkNode;
|
||||||
import bisq.network.p2p.peers.BroadcastHandler;
|
|
||||||
import bisq.network.p2p.peers.Broadcaster;
|
import bisq.network.p2p.peers.Broadcaster;
|
||||||
import bisq.network.p2p.storage.messages.AddDataMessage;
|
import bisq.network.p2p.storage.messages.AddDataMessage;
|
||||||
import bisq.network.p2p.storage.messages.AddPersistableNetworkPayloadMessage;
|
import bisq.network.p2p.storage.messages.AddPersistableNetworkPayloadMessage;
|
||||||
@ -51,10 +50,10 @@ import java.util.HashSet;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
|
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -160,8 +159,7 @@ public class TestState {
|
|||||||
/**
|
/**
|
||||||
* Common test helpers that verify the correct events were signaled based on the test expectation and before/after states.
|
* Common test helpers that verify the correct events were signaled based on the test expectation and before/after states.
|
||||||
*/
|
*/
|
||||||
private void verifySequenceNumberMapWriteContains(P2PDataStorage.ByteArray payloadHash,
|
private void verifySequenceNumberMapWriteContains(P2PDataStorage.ByteArray payloadHash, int sequenceNumber) {
|
||||||
int sequenceNumber) {
|
|
||||||
final ArgumentCaptor<SequenceNumberMap> captor = ArgumentCaptor.forClass(SequenceNumberMap.class);
|
final ArgumentCaptor<SequenceNumberMap> captor = ArgumentCaptor.forClass(SequenceNumberMap.class);
|
||||||
verify(this.mockSeqNrStorage).queueUpForSave(captor.capture(), anyLong());
|
verify(this.mockSeqNrStorage).queueUpForSave(captor.capture(), anyLong());
|
||||||
|
|
||||||
@ -187,10 +185,9 @@ public class TestState {
|
|||||||
verify(this.appendOnlyDataStoreListener, never()).onAdded(persistableNetworkPayload);
|
verify(this.appendOnlyDataStoreListener, never()).onAdded(persistableNetworkPayload);
|
||||||
|
|
||||||
if (expectedBroadcast)
|
if (expectedBroadcast)
|
||||||
verify(this.mockBroadcaster).broadcast(any(AddPersistableNetworkPayloadMessage.class),
|
verify(this.mockBroadcaster).broadcast(any(AddPersistableNetworkPayloadMessage.class), nullable(NodeAddress.class));
|
||||||
nullable(NodeAddress.class), isNull());
|
|
||||||
else
|
else
|
||||||
verify(this.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), nullable(NodeAddress.class), nullable(BroadcastHandler.Listener.class));
|
verify(this.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), nullable(NodeAddress.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
void verifyProtectedStorageAdd(SavedTestState beforeState,
|
void verifyProtectedStorageAdd(SavedTestState beforeState,
|
||||||
@ -219,13 +216,17 @@ public class TestState {
|
|||||||
|
|
||||||
if (expectedBroadcast) {
|
if (expectedBroadcast) {
|
||||||
final ArgumentCaptor<BroadcastMessage> captor = ArgumentCaptor.forClass(BroadcastMessage.class);
|
final ArgumentCaptor<BroadcastMessage> captor = ArgumentCaptor.forClass(BroadcastMessage.class);
|
||||||
|
// If we remove the last argument (isNull()) tests fail. No idea why as the broadcast method has an
|
||||||
|
// overloaded method with nullable listener. Seems a testframework issue as it should not matter if the
|
||||||
|
// method with listener is called with null argument or the other method with no listener. We removed the
|
||||||
|
// null value from all other calls but here we can't as it breaks the test.
|
||||||
verify(this.mockBroadcaster).broadcast(captor.capture(), nullable(NodeAddress.class), isNull());
|
verify(this.mockBroadcaster).broadcast(captor.capture(), nullable(NodeAddress.class), isNull());
|
||||||
|
|
||||||
BroadcastMessage broadcastMessage = captor.getValue();
|
BroadcastMessage broadcastMessage = captor.getValue();
|
||||||
Assert.assertTrue(broadcastMessage instanceof AddDataMessage);
|
Assert.assertTrue(broadcastMessage instanceof AddDataMessage);
|
||||||
Assert.assertEquals(protectedStorageEntry, ((AddDataMessage) broadcastMessage).getProtectedStorageEntry());
|
Assert.assertEquals(protectedStorageEntry, ((AddDataMessage) broadcastMessage).getProtectedStorageEntry());
|
||||||
} else {
|
} else {
|
||||||
verify(this.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), nullable(NodeAddress.class), nullable(BroadcastHandler.Listener.class));
|
verify(this.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), nullable(NodeAddress.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (expectedSequenceNrMapWrite) {
|
if (expectedSequenceNrMapWrite) {
|
||||||
@ -275,7 +276,7 @@ public class TestState {
|
|||||||
verify(this.mockSeqNrStorage, never()).queueUpForSave(any(SequenceNumberMap.class), anyLong());
|
verify(this.mockSeqNrStorage, never()).queueUpForSave(any(SequenceNumberMap.class), anyLong());
|
||||||
|
|
||||||
if (!expectedBroadcast)
|
if (!expectedBroadcast)
|
||||||
verify(this.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), nullable(NodeAddress.class), nullable(BroadcastHandler.Listener.class));
|
verify(this.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), nullable(NodeAddress.class));
|
||||||
|
|
||||||
|
|
||||||
protectedStorageEntries.forEach(protectedStorageEntry -> {
|
protectedStorageEntries.forEach(protectedStorageEntry -> {
|
||||||
@ -287,9 +288,9 @@ public class TestState {
|
|||||||
|
|
||||||
if (expectedBroadcast) {
|
if (expectedBroadcast) {
|
||||||
if (protectedStorageEntry instanceof ProtectedMailboxStorageEntry)
|
if (protectedStorageEntry instanceof ProtectedMailboxStorageEntry)
|
||||||
verify(this.mockBroadcaster).broadcast(any(RemoveMailboxDataMessage.class), nullable(NodeAddress.class), isNull());
|
verify(this.mockBroadcaster).broadcast(any(RemoveMailboxDataMessage.class), nullable(NodeAddress.class));
|
||||||
else
|
else
|
||||||
verify(this.mockBroadcaster).broadcast(any(RemoveDataMessage.class), nullable(NodeAddress.class), isNull());
|
verify(this.mockBroadcaster).broadcast(any(RemoveDataMessage.class), nullable(NodeAddress.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -319,7 +320,7 @@ public class TestState {
|
|||||||
Assert.assertTrue(entryAfterRefresh.getCreationTimeStamp() > beforeState.creationTimestampBeforeUpdate);
|
Assert.assertTrue(entryAfterRefresh.getCreationTimeStamp() > beforeState.creationTimestampBeforeUpdate);
|
||||||
|
|
||||||
final ArgumentCaptor<BroadcastMessage> captor = ArgumentCaptor.forClass(BroadcastMessage.class);
|
final ArgumentCaptor<BroadcastMessage> captor = ArgumentCaptor.forClass(BroadcastMessage.class);
|
||||||
verify(this.mockBroadcaster).broadcast(captor.capture(), nullable(NodeAddress.class), isNull());
|
verify(this.mockBroadcaster).broadcast(captor.capture(), nullable(NodeAddress.class));
|
||||||
|
|
||||||
BroadcastMessage broadcastMessage = captor.getValue();
|
BroadcastMessage broadcastMessage = captor.getValue();
|
||||||
Assert.assertTrue(broadcastMessage instanceof RefreshOfferMessage);
|
Assert.assertTrue(broadcastMessage instanceof RefreshOfferMessage);
|
||||||
@ -336,7 +337,7 @@ public class TestState {
|
|||||||
Assert.assertEquals(beforeState.creationTimestampBeforeUpdate, entryAfterRefresh.getCreationTimeStamp());
|
Assert.assertEquals(beforeState.creationTimestampBeforeUpdate, entryAfterRefresh.getCreationTimeStamp());
|
||||||
}
|
}
|
||||||
|
|
||||||
verify(this.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), nullable(NodeAddress.class), nullable(BroadcastHandler.Listener.class));
|
verify(this.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), nullable(NodeAddress.class));
|
||||||
verify(this.mockSeqNrStorage, never()).queueUpForSave(any(SequenceNumberMap.class), anyLong());
|
verify(this.mockSeqNrStorage, never()).queueUpForSave(any(SequenceNumberMap.class), anyLong());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -639,7 +639,10 @@ message Filter {
|
|||||||
repeated string refundAgents = 18;
|
repeated string refundAgents = 18;
|
||||||
repeated string bannedSignerPubKeys = 19;
|
repeated string bannedSignerPubKeys = 19;
|
||||||
repeated string btc_fee_receiver_addresses = 20;
|
repeated string btc_fee_receiver_addresses = 20;
|
||||||
bool disable_auto_conf = 21;
|
int64 creation_date = 21;
|
||||||
|
string signer_pub_key_as_hex = 22;
|
||||||
|
repeated string bannedPrivilegedDevPubKeys = 23;
|
||||||
|
bool disable_auto_conf = 24;
|
||||||
}
|
}
|
||||||
|
|
||||||
// not used anymore from v0.6 on. But leave it for receiving TradeStatistics objects from older
|
// not used anymore from v0.6 on. But leave it for receiving TradeStatistics objects from older
|
||||||
@ -1087,6 +1090,7 @@ message PopmoneyAccountPayload {
|
|||||||
|
|
||||||
message RevolutAccountPayload {
|
message RevolutAccountPayload {
|
||||||
string account_id = 1;
|
string account_id = 1;
|
||||||
|
string user_name = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message PerfectMoneyAccountPayload {
|
message PerfectMoneyAccountPayload {
|
||||||
|
Loading…
Reference in New Issue
Block a user