mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-20 02:12:00 +01:00
Merge branch 'release-candidate-0.9.0' into improve-backward-compatibility-with-new-arb-selection
This commit is contained in:
commit
735e4588ce
3
.gitignore
vendored
3
.gitignore
vendored
@ -21,7 +21,8 @@ build
|
||||
*.java.hsp
|
||||
*.~ava
|
||||
/bundles
|
||||
/bisq*
|
||||
/bisq-*
|
||||
/lib
|
||||
/xchange
|
||||
desktop.ini
|
||||
*/target/*
|
||||
|
53
build.gradle
53
build.gradle
@ -11,6 +11,14 @@ buildscript {
|
||||
}
|
||||
}
|
||||
|
||||
configure(rootProject) {
|
||||
// remove the 'bisq-*' scripts and 'lib' dir generated by the 'installDist' task
|
||||
task clean {
|
||||
doLast {
|
||||
delete fileTree(dir: rootProject.projectDir, include: 'bisq-*'), 'lib'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configure(subprojects) {
|
||||
apply plugin: 'java'
|
||||
@ -57,11 +65,56 @@ configure([project(':desktop'),
|
||||
project(':relay'),
|
||||
project(':seednode'),
|
||||
project(':statsnode')]) {
|
||||
|
||||
apply plugin: 'application'
|
||||
|
||||
build.dependsOn installDist
|
||||
installDist.destinationDir = file('build/app')
|
||||
distZip.enabled = false
|
||||
|
||||
// the 'installDist' and 'startScripts' blocks below configure bisq executables to put
|
||||
// generated shell scripts in the root project directory, such that users can easily
|
||||
// discover and invoke e.g. ./bisq-desktop, ./bisq-seednode, etc.
|
||||
// See https://stackoverflow.com/q/46327736 for details.
|
||||
|
||||
installDist {
|
||||
doLast {
|
||||
// copy generated shell scripts, e.g. `bisq-desktop` directly to the project
|
||||
// root directory for discoverability and ease of use
|
||||
copy {
|
||||
from "$destinationDir/bin"
|
||||
into rootProject.projectDir
|
||||
}
|
||||
// copy libs required for generated shell script classpaths to 'lib' dir under
|
||||
// the project root directory
|
||||
copy {
|
||||
from "$destinationDir/lib"
|
||||
into "${rootProject.projectDir}/lib"
|
||||
}
|
||||
|
||||
if (osdetector.os != 'windows')
|
||||
delete fileTree(dir: rootProject.projectDir, include: 'bisq-*.bat')
|
||||
else
|
||||
delete fileTree(dir: rootProject.projectDir, include: 'bisq-*', exclude: '*.bat')
|
||||
}
|
||||
}
|
||||
|
||||
startScripts {
|
||||
// rename scripts from, e.g. `desktop` to `bisq-desktop`
|
||||
applicationName = "bisq-$applicationName"
|
||||
|
||||
// edit generated shell scripts such that they expect to be executed in the
|
||||
// project root dir as opposed to a 'bin' subdirectory
|
||||
doLast {
|
||||
def windowsScriptFile = file getWindowsScript()
|
||||
windowsScriptFile.text = windowsScriptFile.text.replace(
|
||||
'set APP_HOME=%DIRNAME%..', 'set APP_HOME=%DIRNAME%')
|
||||
|
||||
def unixScriptFile = file getUnixScript()
|
||||
unixScriptFile.text = unixScriptFile.text.replace(
|
||||
'cd "`dirname \\"$PRG\\"`/.." >/dev/null', 'cd "`dirname \\"$PRG\\"`" >/dev/null')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -723,6 +723,7 @@ message PaymentAccountPayload {
|
||||
MoneyGramAccountPayload money_gram_account_payload = 23;
|
||||
HalCashAccountPayload hal_cash_account_payload = 24;
|
||||
PromptPayAccountPayload prompt_pay_account_payload = 25;
|
||||
AdvancedCashAccountPayload advanced_cash_account_payload = 26;
|
||||
}
|
||||
map<string, string> exclude_from_json_data = 15;
|
||||
}
|
||||
@ -901,6 +902,11 @@ message PromptPayAccountPayload {
|
||||
string prompt_pay_id = 1;
|
||||
}
|
||||
|
||||
message AdvancedCashAccountPayload {
|
||||
string account_nr = 1;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PersistableEnvelope
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -30,7 +30,6 @@ import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.setup.CorePersistedDataHost;
|
||||
import bisq.core.setup.CoreSetup;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.util.joptsimple.EnumValueConverter;
|
||||
|
||||
import bisq.network.NetworkOptionKeys;
|
||||
import bisq.network.p2p.P2PService;
|
||||
@ -44,10 +43,8 @@ import bisq.common.proto.persistable.PersistedDataHost;
|
||||
import bisq.common.setup.GracefulShutDownHandler;
|
||||
import bisq.common.storage.CorruptedDatabaseFilesHandler;
|
||||
import bisq.common.storage.Storage;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import org.springframework.core.env.JOptCommandLinePropertySource;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import joptsimple.OptionException;
|
||||
import joptsimple.OptionParser;
|
||||
@ -72,25 +69,39 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import static bisq.core.app.BisqEnvironment.DEFAULT_APP_NAME;
|
||||
import static bisq.core.app.BisqEnvironment.DEFAULT_USER_DATA_DIR;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static java.lang.String.format;
|
||||
import static java.lang.String.join;
|
||||
|
||||
@Slf4j
|
||||
public abstract class BisqExecutable implements GracefulShutDownHandler {
|
||||
|
||||
private final String fullName;
|
||||
private final String scriptName;
|
||||
private final String version;
|
||||
|
||||
protected Injector injector;
|
||||
protected AppModule module;
|
||||
protected BisqEnvironment bisqEnvironment;
|
||||
|
||||
public BisqExecutable(String fullName, String scriptName, String version) {
|
||||
this.fullName = fullName;
|
||||
this.scriptName = scriptName;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public static boolean setupInitialOptionParser(String[] args) throws IOException {
|
||||
// We don't want to do the full argument parsing here as that might easily change in update versions
|
||||
// So we only handle the absolute minimum which is APP_NAME, APP_DATA_DIR_KEY and USER_DATA_DIR
|
||||
OptionParser parser = new OptionParser();
|
||||
parser.allowsUnrecognizedOptions();
|
||||
parser.accepts(AppOptionKeys.USER_DATA_DIR_KEY, description("User data directory", DEFAULT_USER_DATA_DIR))
|
||||
.withRequiredArg();
|
||||
parser.accepts(AppOptionKeys.APP_NAME_KEY, description("Application name", DEFAULT_APP_NAME))
|
||||
.withRequiredArg();
|
||||
|
||||
parser.accepts(AppOptionKeys.USER_DATA_DIR_KEY,
|
||||
"User data directory")
|
||||
.withRequiredArg()
|
||||
.defaultsTo(DEFAULT_USER_DATA_DIR);
|
||||
|
||||
parser.accepts(AppOptionKeys.APP_NAME_KEY,
|
||||
"Application name")
|
||||
.withRequiredArg()
|
||||
.defaultsTo(DEFAULT_APP_NAME);
|
||||
|
||||
OptionSet options;
|
||||
try {
|
||||
@ -114,6 +125,7 @@ public abstract class BisqExecutable implements GracefulShutDownHandler {
|
||||
|
||||
public void execute(String[] args) throws Exception {
|
||||
OptionParser parser = new OptionParser();
|
||||
parser.formatHelpWith(new BisqHelpFormatter(fullName, scriptName, version));
|
||||
parser.accepts(HELP_KEY, "This help text").forHelp();
|
||||
|
||||
this.customizeOptionParsing(parser);
|
||||
@ -166,7 +178,7 @@ public abstract class BisqExecutable implements GracefulShutDownHandler {
|
||||
} catch (OptionException e) {
|
||||
// unfortunately, the OptionArgumentConversionException is not visible so we cannot catch only those.
|
||||
// hence, workaround
|
||||
if(e.getCause() != null)
|
||||
if (e.getCause() != null)
|
||||
// get something like "Error while parsing application parameter '--torrcFile': File [/path/to/file] does not exist"
|
||||
System.err.println("Error while parsing application parameter '--" + e.options().get(0) + "': " + e.getCause().getMessage());
|
||||
else
|
||||
@ -311,191 +323,253 @@ public abstract class BisqExecutable implements GracefulShutDownHandler {
|
||||
protected void customizeOptionParsing(OptionParser parser) {
|
||||
//CommonOptionKeys
|
||||
parser.accepts(CommonOptionKeys.LOG_LEVEL_KEY,
|
||||
description("Log level [OFF, ALL, ERROR, WARN, INFO, DEBUG, TRACE]", BisqEnvironment.LOG_LEVEL_DEFAULT))
|
||||
.withRequiredArg();
|
||||
"Log level")
|
||||
.withRequiredArg()
|
||||
.describedAs("OFF|ALL|ERROR|WARN|INFO|DEBUG|TRACE")
|
||||
.defaultsTo(BisqEnvironment.LOG_LEVEL_DEFAULT);
|
||||
|
||||
//NetworkOptionKeys
|
||||
parser.accepts(NetworkOptionKeys.SEED_NODES_KEY,
|
||||
description("Override hard coded seed nodes as comma separated list: E.g. rxdkppp3vicnbgqt.onion:8002, mfla72c4igh5ta2t.onion:8002", ""))
|
||||
.withRequiredArg();
|
||||
"Override hard coded seed nodes as comma separated list e.g. " +
|
||||
"'rxdkppp3vicnbgqt.onion:8002,mfla72c4igh5ta2t.onion:8002'")
|
||||
.withRequiredArg()
|
||||
.describedAs("host:port[,...]");
|
||||
|
||||
parser.accepts(NetworkOptionKeys.MY_ADDRESS,
|
||||
description("My own onion address (used for bootstrap nodes to exclude itself)", ""))
|
||||
.withRequiredArg();
|
||||
"My own onion address (used for bootstrap nodes to exclude itself)")
|
||||
.withRequiredArg()
|
||||
.describedAs("host:port");
|
||||
|
||||
parser.accepts(NetworkOptionKeys.BAN_LIST,
|
||||
description("Nodes to exclude from network connections.", ""))
|
||||
.withRequiredArg();
|
||||
"Nodes to exclude from network connections.")
|
||||
.withRequiredArg()
|
||||
.describedAs("host:port[,...]");
|
||||
|
||||
// use a fixed port as arbitrator use that for his ID
|
||||
parser.accepts(NetworkOptionKeys.PORT_KEY,
|
||||
description("Port to listen on", 9999))
|
||||
"Port to listen on")
|
||||
.withRequiredArg()
|
||||
.ofType(int.class);
|
||||
.ofType(int.class)
|
||||
.defaultsTo(9999);
|
||||
|
||||
parser.accepts(NetworkOptionKeys.USE_LOCALHOST_FOR_P2P,
|
||||
description("Use localhost P2P network for development", false))
|
||||
"Use localhost P2P network for development")
|
||||
.withRequiredArg()
|
||||
.ofType(boolean.class);
|
||||
.ofType(boolean.class)
|
||||
.defaultsTo(false);
|
||||
|
||||
parser.accepts(NetworkOptionKeys.MAX_CONNECTIONS,
|
||||
description("Max. connections a peer will try to keep", P2PService.MAX_CONNECTIONS_DEFAULT))
|
||||
"Max. connections a peer will try to keep")
|
||||
.withRequiredArg()
|
||||
.ofType(int.class);
|
||||
.ofType(int.class)
|
||||
.defaultsTo(P2PService.MAX_CONNECTIONS_DEFAULT);
|
||||
|
||||
parser.accepts(NetworkOptionKeys.SOCKS_5_PROXY_BTC_ADDRESS,
|
||||
description("A proxy address to be used for Bitcoin network. [host:port]", ""))
|
||||
.withRequiredArg();
|
||||
"A proxy address to be used for Bitcoin network.")
|
||||
.withRequiredArg()
|
||||
.describedAs("host:port");
|
||||
|
||||
parser.accepts(NetworkOptionKeys.SOCKS_5_PROXY_HTTP_ADDRESS,
|
||||
description("A proxy address to be used for Http requests (should be non-Tor). [host:port]", ""))
|
||||
.withRequiredArg();
|
||||
"A proxy address to be used for Http requests (should be non-Tor)")
|
||||
.withRequiredArg()
|
||||
.describedAs("host:port");
|
||||
|
||||
parser.accepts(NetworkOptionKeys.TORRC_FILE,
|
||||
description("An existing torrc-file to be sourced for Tor. Note that torrc-entries, which are critical to Bisqs flawless operation, cannot be overwritten.", ""))
|
||||
"An existing torrc-file to be sourced for Tor. Note that torrc-entries, " +
|
||||
"which are critical to Bisq's flawless operation, cannot be overwritten.")
|
||||
.withRequiredArg()
|
||||
.withValuesConvertedBy(new PathConverter(PathProperties.FILE_EXISTING, PathProperties.READABLE));
|
||||
|
||||
parser.accepts(NetworkOptionKeys.TORRC_OPTIONS,
|
||||
description("A list of torrc-entries to amend to Bisqs torrc. Note that torrc-entries, which are critical to Bisqs flawless operation, cannot be overwritten. [torrc options line, torrc option, ...]", ""))
|
||||
"A list of torrc-entries to amend to Bisq's torrc. Note that torrc-entries," +
|
||||
"which are critical to Bisq's flawless operation, cannot be overwritten. " +
|
||||
"[torrc options line, torrc option, ...]")
|
||||
.withRequiredArg()
|
||||
.withValuesConvertedBy(RegexMatcher.regex("^([^\\s,]+\\s[^,]+,?\\s*)+$"));
|
||||
|
||||
parser.accepts(NetworkOptionKeys.EXTERNAL_TOR_CONTROL_PORT,
|
||||
description("The control port of an already running Tor service to be used by Bisq [port].", ""))
|
||||
"The control port of an already running Tor service to be used by Bisq.")
|
||||
.availableUnless(NetworkOptionKeys.TORRC_FILE, NetworkOptionKeys.TORRC_OPTIONS)
|
||||
.withRequiredArg()
|
||||
.ofType(int.class);
|
||||
.ofType(int.class)
|
||||
.describedAs("port");
|
||||
|
||||
parser.accepts(NetworkOptionKeys.EXTERNAL_TOR_PASSWORD,
|
||||
description("The password for controlling the already running Tor service.", ""))
|
||||
"The password for controlling the already running Tor service.")
|
||||
.availableIf(NetworkOptionKeys.EXTERNAL_TOR_CONTROL_PORT)
|
||||
.withRequiredArg();
|
||||
|
||||
parser.accepts(NetworkOptionKeys.EXTERNAL_TOR_COOKIE_FILE,
|
||||
description("The cookie file for authenticating against the already running Tor service. Use in conjunction with --" + NetworkOptionKeys.EXTERNAL_TOR_USE_SAFECOOKIE, ""))
|
||||
"The cookie file for authenticating against the already running Tor service. " +
|
||||
"Use in conjunction with --" + NetworkOptionKeys.EXTERNAL_TOR_USE_SAFECOOKIE)
|
||||
.availableIf(NetworkOptionKeys.EXTERNAL_TOR_CONTROL_PORT)
|
||||
.availableUnless(NetworkOptionKeys.EXTERNAL_TOR_PASSWORD)
|
||||
.withRequiredArg()
|
||||
.withValuesConvertedBy(new PathConverter(PathProperties.FILE_EXISTING, PathProperties.READABLE));
|
||||
|
||||
parser.accepts(NetworkOptionKeys.EXTERNAL_TOR_USE_SAFECOOKIE,
|
||||
description("Use the SafeCookie method when authenticating to the already running Tor service.", ""))
|
||||
"Use the SafeCookie method when authenticating to the already running Tor service.")
|
||||
.availableIf(NetworkOptionKeys.EXTERNAL_TOR_COOKIE_FILE);
|
||||
|
||||
//AppOptionKeys
|
||||
parser.accepts(AppOptionKeys.USER_DATA_DIR_KEY,
|
||||
description("User data directory", BisqEnvironment.DEFAULT_USER_DATA_DIR))
|
||||
.withRequiredArg();
|
||||
"User data directory")
|
||||
.withRequiredArg()
|
||||
.defaultsTo(BisqEnvironment.DEFAULT_USER_DATA_DIR);
|
||||
|
||||
parser.accepts(AppOptionKeys.APP_NAME_KEY,
|
||||
description("Application name", BisqEnvironment.DEFAULT_APP_NAME))
|
||||
.withRequiredArg();
|
||||
"Application name")
|
||||
.withRequiredArg()
|
||||
.defaultsTo(BisqEnvironment.DEFAULT_APP_NAME);
|
||||
|
||||
parser.accepts(AppOptionKeys.MAX_MEMORY,
|
||||
description("Max. permitted memory (used only at headless versions)", 600))
|
||||
.withRequiredArg();
|
||||
"Max. permitted memory (used only at headless versions)")
|
||||
.withRequiredArg()
|
||||
.defaultsTo("600");
|
||||
|
||||
parser.accepts(AppOptionKeys.APP_DATA_DIR_KEY,
|
||||
description("Application data directory", BisqEnvironment.DEFAULT_APP_DATA_DIR))
|
||||
.withRequiredArg();
|
||||
"Application data directory")
|
||||
.withRequiredArg()
|
||||
.defaultsTo(BisqEnvironment.DEFAULT_APP_DATA_DIR);
|
||||
|
||||
parser.accepts(AppOptionKeys.IGNORE_DEV_MSG_KEY,
|
||||
description("If set to true all signed network_messages from bisq developers are ignored " +
|
||||
"(Global alert, Version update alert, Filters for offers, nodes or trading account data)", false))
|
||||
"If set to true all signed network_messages from bisq developers are ignored " +
|
||||
"(Global alert, Version update alert, Filters for offers, nodes or trading account data)")
|
||||
.withRequiredArg()
|
||||
.ofType(boolean.class);
|
||||
.ofType(boolean.class)
|
||||
.defaultsTo(false);
|
||||
|
||||
parser.accepts(AppOptionKeys.DESKTOP_WITH_HTTP_API,
|
||||
description("If set to true Bisq Desktop starts with Http API", false))
|
||||
"If set to true Bisq Desktop starts with Http API")
|
||||
.withRequiredArg()
|
||||
.ofType(boolean.class);
|
||||
.ofType(boolean.class)
|
||||
.defaultsTo(false);
|
||||
|
||||
parser.accepts(AppOptionKeys.DESKTOP_WITH_GRPC_API,
|
||||
description("If set to true Bisq Desktop starts with gRPC API", false))
|
||||
"If set to true Bisq Desktop starts with gRPC API")
|
||||
.withRequiredArg()
|
||||
.ofType(boolean.class);
|
||||
.ofType(boolean.class)
|
||||
.defaultsTo(false);
|
||||
|
||||
parser.accepts(AppOptionKeys.USE_DEV_PRIVILEGE_KEYS,
|
||||
description("If that is true all the privileged features which requires a private key to enable it are overridden by a dev key pair " +
|
||||
"(This is for developers only!)", false))
|
||||
"If that is true all the privileged features which requires a private key " +
|
||||
"to enable it are overridden by a dev key pair (This is for developers only!)")
|
||||
.withRequiredArg()
|
||||
.ofType(boolean.class);
|
||||
.ofType(boolean.class)
|
||||
.defaultsTo(false);
|
||||
|
||||
parser.accepts(AppOptionKeys.REFERRAL_ID,
|
||||
description("Optional Referral ID (e.g. for API users or pro market makers)", ""))
|
||||
"Optional Referral ID (e.g. for API users or pro market makers)")
|
||||
.withRequiredArg();
|
||||
|
||||
parser.accepts(CommonOptionKeys.USE_DEV_MODE,
|
||||
description("Enables dev mode which is used for convenience for developer testing", false))
|
||||
"Enables dev mode which is used for convenience for developer testing")
|
||||
.withRequiredArg()
|
||||
.ofType(boolean.class);
|
||||
.ofType(boolean.class)
|
||||
.defaultsTo(false);
|
||||
|
||||
parser.accepts(AppOptionKeys.DUMP_STATISTICS,
|
||||
description("If set to true the trade statistics are stored as json file in the data dir.", false))
|
||||
"If set to true the trade statistics are stored as json file in the data dir.")
|
||||
.withRequiredArg()
|
||||
.ofType(boolean.class);
|
||||
.ofType(boolean.class)
|
||||
.defaultsTo(false);
|
||||
|
||||
parser.accepts(AppOptionKeys.PROVIDERS,
|
||||
description("Custom providers (comma separated)", false))
|
||||
.withRequiredArg();
|
||||
"Custom providers (comma separated)")
|
||||
.withRequiredArg()
|
||||
.describedAs("host:port[,...]");
|
||||
|
||||
//BtcOptionKeys
|
||||
parser.accepts(BtcOptionKeys.BASE_CURRENCY_NETWORK,
|
||||
description("Base currency network", BisqEnvironment.getDefaultBaseCurrencyNetwork().name()))
|
||||
"Base currency network")
|
||||
.withRequiredArg()
|
||||
.ofType(String.class);
|
||||
//.withValuesConvertedBy(new EnumValueConverter(String.class));
|
||||
parser.accepts(BtcOptionKeys.REG_TEST_HOST,
|
||||
description("", RegTestHost.DEFAULT))
|
||||
.ofType(String.class)
|
||||
.defaultsTo(BisqEnvironment.getDefaultBaseCurrencyNetwork().name());
|
||||
|
||||
parser.accepts(BtcOptionKeys.REG_TEST_HOST)
|
||||
.withRequiredArg()
|
||||
.ofType(RegTestHost.class)
|
||||
.withValuesConvertedBy(new EnumValueConverter(RegTestHost.class));
|
||||
.defaultsTo(RegTestHost.DEFAULT);
|
||||
|
||||
parser.accepts(BtcOptionKeys.BTC_NODES,
|
||||
description("Custom nodes used for BitcoinJ as comma separated IP addresses.", ""))
|
||||
.withRequiredArg();
|
||||
"Custom nodes used for BitcoinJ as comma separated IP addresses.")
|
||||
.withRequiredArg()
|
||||
.describedAs("ip[,...]");
|
||||
|
||||
parser.accepts(BtcOptionKeys.USE_TOR_FOR_BTC,
|
||||
description("If set to true BitcoinJ is routed over tor (socks 5 proxy).", ""))
|
||||
.withRequiredArg();
|
||||
parser.accepts(BtcOptionKeys.SOCKS5_DISCOVER_MODE,
|
||||
description("Specify discovery mode for Bitcoin nodes. One or more of: [ADDR, DNS, ONION, ALL]" +
|
||||
" (comma separated, they get OR'd together). Default value is ALL", "ALL"))
|
||||
.withRequiredArg();
|
||||
parser.accepts(BtcOptionKeys.USE_ALL_PROVIDED_NODES,
|
||||
description("Set to true if connection of bitcoin nodes should include clear net nodes", ""))
|
||||
.withRequiredArg();
|
||||
parser.accepts(BtcOptionKeys.USER_AGENT,
|
||||
description("User agent at btc node connections", ""))
|
||||
.withRequiredArg();
|
||||
parser.accepts(BtcOptionKeys.NUM_CONNECTIONS_FOR_BTC,
|
||||
description("Number of connections to the Bitcoin network", "9"))
|
||||
"If set to true BitcoinJ is routed over tor (socks 5 proxy).")
|
||||
.withRequiredArg();
|
||||
|
||||
parser.accepts(BtcOptionKeys.SOCKS5_DISCOVER_MODE,
|
||||
"Specify discovery mode for Bitcoin nodes. One or more of: [ADDR, DNS, ONION, ALL]" +
|
||||
" (comma separated, they get OR'd together).")
|
||||
.withRequiredArg()
|
||||
.describedAs("mode[,...]")
|
||||
.defaultsTo("ALL");
|
||||
|
||||
parser.accepts(BtcOptionKeys.USE_ALL_PROVIDED_NODES,
|
||||
"Set to true if connection of bitcoin nodes should include clear net nodes")
|
||||
.withRequiredArg();
|
||||
|
||||
parser.accepts(BtcOptionKeys.USER_AGENT,
|
||||
"User agent at btc node connections")
|
||||
.withRequiredArg();
|
||||
|
||||
parser.accepts(BtcOptionKeys.NUM_CONNECTIONS_FOR_BTC,
|
||||
"Number of connections to the Bitcoin network")
|
||||
.withRequiredArg()
|
||||
.defaultsTo("9");
|
||||
|
||||
//RpcOptionKeys
|
||||
parser.accepts(DaoOptionKeys.RPC_USER,
|
||||
description("Bitcoind rpc username", ""))
|
||||
"Bitcoind rpc username")
|
||||
.withRequiredArg();
|
||||
|
||||
parser.accepts(DaoOptionKeys.RPC_PASSWORD,
|
||||
description("Bitcoind rpc password", ""))
|
||||
"Bitcoind rpc password")
|
||||
.withRequiredArg();
|
||||
|
||||
parser.accepts(DaoOptionKeys.RPC_PORT,
|
||||
description("Bitcoind rpc port", ""))
|
||||
"Bitcoind rpc port")
|
||||
.withRequiredArg();
|
||||
|
||||
parser.accepts(DaoOptionKeys.RPC_BLOCK_NOTIFICATION_PORT,
|
||||
description("Bitcoind rpc port for block notifications", ""))
|
||||
"Bitcoind rpc port for block notifications")
|
||||
.withRequiredArg();
|
||||
|
||||
parser.accepts(DaoOptionKeys.DUMP_BLOCKCHAIN_DATA,
|
||||
description("If set to true the blockchain data from RPC requests to Bitcoin Core are stored " +
|
||||
"as json file in the data dir.", false))
|
||||
"If set to true the blockchain data from RPC requests to Bitcoin Core are " +
|
||||
"stored as json file in the data dir.")
|
||||
.withRequiredArg()
|
||||
.ofType(boolean.class);
|
||||
.ofType(boolean.class)
|
||||
.defaultsTo(false);
|
||||
|
||||
parser.accepts(DaoOptionKeys.FULL_DAO_NODE,
|
||||
description("If set to true the node requests the blockchain data via RPC requests from Bitcoin Core and " +
|
||||
"provide the validated BSQ txs to the network. It requires that the other RPC properties are " +
|
||||
"set as well.", ""))
|
||||
"If set to true the node requests the blockchain data via RPC requests " +
|
||||
"from Bitcoin Core and provide the validated BSQ txs to the network. " +
|
||||
"It requires that the other RPC properties are set as well.")
|
||||
.withRequiredArg();
|
||||
|
||||
parser.accepts(DaoOptionKeys.GENESIS_TX_ID,
|
||||
description("Genesis transaction ID when not using the hard coded one", ""))
|
||||
"Genesis transaction ID when not using the hard coded one")
|
||||
.withRequiredArg();
|
||||
|
||||
parser.accepts(DaoOptionKeys.GENESIS_BLOCK_HEIGHT,
|
||||
description("Genesis transaction block height when not using the hard coded one", -1))
|
||||
.withRequiredArg();
|
||||
parser.accepts(DaoOptionKeys.DAO_ACTIVATED,
|
||||
description("Developer flag. If true it enables dao phase 2 features.", false))
|
||||
"Genesis transaction block height when not using the hard coded one")
|
||||
.withRequiredArg()
|
||||
.ofType(boolean.class);
|
||||
.defaultsTo("-1");
|
||||
|
||||
parser.accepts(DaoOptionKeys.DAO_ACTIVATED,
|
||||
"Developer flag. If true it enables dao phase 2 features.")
|
||||
.withRequiredArg()
|
||||
.ofType(boolean.class)
|
||||
.defaultsTo(false);
|
||||
}
|
||||
|
||||
public static BisqEnvironment getBisqEnvironment(OptionSet options) {
|
||||
return new BisqEnvironment(new JOptCommandLinePropertySource(BisqEnvironment.BISQ_COMMANDLINE_PROPERTY_SOURCE_NAME, checkNotNull(options)));
|
||||
}
|
||||
|
||||
protected static String description(String descText, Object defaultValue) {
|
||||
String description = "";
|
||||
if (StringUtils.hasText(descText))
|
||||
description = description.concat(descText);
|
||||
if (defaultValue != null)
|
||||
description = join(" ", description, format("(default: %s)", defaultValue));
|
||||
return description;
|
||||
}
|
||||
|
||||
public static void initAppDir(String appDir) {
|
||||
Path dir = Paths.get(appDir);
|
||||
if (Files.exists(dir)) {
|
||||
|
@ -21,6 +21,7 @@ import bisq.core.CoreModule;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.app.AppModule;
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.setup.CommonSetup;
|
||||
|
||||
import joptsimple.OptionSet;
|
||||
@ -36,6 +37,10 @@ import lombok.extern.slf4j.Slf4j;
|
||||
public class BisqHeadlessAppMain extends BisqExecutable {
|
||||
protected HeadlessApp headlessApp;
|
||||
|
||||
public BisqHeadlessAppMain() {
|
||||
super("Bisq Daemon", "bisqd", Version.VERSION);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (BisqExecutable.setupInitialOptionParser(args)) {
|
||||
// For some reason the JavaFX launch process results in us losing the thread context class loader: reset it.
|
||||
|
131
core/src/main/java/bisq/core/app/BisqHelpFormatter.java
Normal file
131
core/src/main/java/bisq/core/app/BisqHelpFormatter.java
Normal file
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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.core.app;
|
||||
|
||||
import joptsimple.HelpFormatter;
|
||||
import joptsimple.OptionDescriptor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class BisqHelpFormatter implements HelpFormatter {
|
||||
|
||||
private final String fullName;
|
||||
private final String scriptName;
|
||||
private final String version;
|
||||
|
||||
public BisqHelpFormatter(String fullName, String scriptName, String version) {
|
||||
this.fullName = fullName;
|
||||
this.scriptName = scriptName;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String format(Map<String, ? extends OptionDescriptor> descriptors) {
|
||||
|
||||
StringBuilder output = new StringBuilder();
|
||||
output.append(String.format("%s version %s\n\n", fullName, version));
|
||||
output.append(String.format("Usage: %s [options]\n\n", scriptName));
|
||||
output.append("Options:\n\n");
|
||||
|
||||
for (Map.Entry<String, ? extends OptionDescriptor> entry : descriptors.entrySet()) {
|
||||
String optionName = entry.getKey();
|
||||
OptionDescriptor optionDesc = entry.getValue();
|
||||
|
||||
if (optionDesc.representsNonOptions())
|
||||
continue;
|
||||
|
||||
output.append(String.format("%s\n", formatOptionSyntax(optionName, optionDesc)));
|
||||
output.append(String.format("%s\n", formatOptionDescription(optionDesc)));
|
||||
}
|
||||
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
private String formatOptionSyntax(String optionName, OptionDescriptor optionDesc) {
|
||||
StringBuilder result = new StringBuilder(String.format(" --%s", optionName));
|
||||
|
||||
if (optionDesc.acceptsArguments())
|
||||
result.append(String.format("=<%s>", formatArgDescription(optionDesc)));
|
||||
|
||||
List<?> defaultValues = optionDesc.defaultValues();
|
||||
if (defaultValues.size() > 0)
|
||||
result.append(String.format(" (default: %s)", formatDefaultValues(defaultValues)));
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private String formatArgDescription(OptionDescriptor optionDesc) {
|
||||
String argDescription = optionDesc.argumentDescription();
|
||||
|
||||
if (argDescription.length() > 0)
|
||||
return argDescription;
|
||||
|
||||
String typeIndicator = optionDesc.argumentTypeIndicator();
|
||||
|
||||
if (typeIndicator == null)
|
||||
return "value";
|
||||
|
||||
try {
|
||||
Class<?> type = Class.forName(typeIndicator);
|
||||
return type.isEnum() ?
|
||||
Arrays.stream(type.getEnumConstants()).map(Object::toString).collect(Collectors.joining("|")) :
|
||||
typeIndicator.substring(typeIndicator.lastIndexOf('.') + 1);
|
||||
} catch (ClassNotFoundException ex) {
|
||||
// typeIndicator is something other than a class name, which can occur
|
||||
// in certain cases e.g. where OptionParser.withValuesConvertedBy is used.
|
||||
return typeIndicator;
|
||||
}
|
||||
}
|
||||
|
||||
private Object formatDefaultValues(List<?> defaultValues) {
|
||||
return defaultValues.size() == 1 ?
|
||||
defaultValues.get(0) :
|
||||
defaultValues.toString();
|
||||
}
|
||||
|
||||
private String formatOptionDescription(OptionDescriptor optionDesc) {
|
||||
StringBuilder output = new StringBuilder();
|
||||
|
||||
String remainder = optionDesc.description().trim();
|
||||
|
||||
// Wrap description text at 80 characters with 8 spaces of indentation and a
|
||||
// maximum of 72 chars of text, wrapping on spaces. Strings longer than 72 chars
|
||||
// without any spaces (e.g. a URL) are allowed to overflow the 80-char margin.
|
||||
while (remainder.length() > 72) {
|
||||
int idxFirstSpace = remainder.indexOf(' ');
|
||||
int chunkLen = idxFirstSpace == -1 ? remainder.length() : idxFirstSpace > 73 ? idxFirstSpace : 73;
|
||||
String chunk = remainder.substring(0, chunkLen);
|
||||
int idxLastSpace = chunk.lastIndexOf(' ');
|
||||
int idxBreak = idxLastSpace > 0 ? idxLastSpace : chunk.length();
|
||||
String line = remainder.substring(0, idxBreak);
|
||||
output.append(formatLine(line));
|
||||
remainder = remainder.substring(chunk.length() - (chunk.length() - idxBreak)).trim();
|
||||
}
|
||||
|
||||
if (remainder.length() > 0)
|
||||
output.append(formatLine(remainder));
|
||||
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
private String formatLine(String line) {
|
||||
return String.format(" %s\n", line.trim());
|
||||
}
|
||||
}
|
@ -51,6 +51,10 @@ public abstract class ExecutableForAppWithP2p extends BisqExecutable implements
|
||||
private volatile boolean stopped;
|
||||
private static long maxMemory = MAX_MEMORY_MB_DEFAULT;
|
||||
|
||||
public ExecutableForAppWithP2p(String fullName, String scriptName, String version) {
|
||||
super(fullName, scriptName, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configUserThread() {
|
||||
final ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
|
@ -147,6 +147,20 @@ public class CurrencyUtil {
|
||||
}
|
||||
|
||||
// At OKPay you can exchange internally those currencies
|
||||
public static List<TradeCurrency> getAllAdvancedCashCurrencies() {
|
||||
ArrayList<TradeCurrency> currencies = new ArrayList<>(Arrays.asList(
|
||||
new FiatCurrency("USD"),
|
||||
new FiatCurrency("EUR"),
|
||||
new FiatCurrency("GBP"),
|
||||
new FiatCurrency("RUB"),
|
||||
new FiatCurrency("UAH"),
|
||||
new FiatCurrency("KZT"),
|
||||
new FiatCurrency("BRL")
|
||||
));
|
||||
currencies.sort(Comparator.comparing(TradeCurrency::getCode));
|
||||
return currencies;
|
||||
}
|
||||
|
||||
public static List<TradeCurrency> getAllOKPayCurrencies() {
|
||||
ArrayList<TradeCurrency> currencies = new ArrayList<>(Arrays.asList(
|
||||
new FiatCurrency("EUR"),
|
||||
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.core.payment;
|
||||
|
||||
import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.payment.payload.AdvancedCashAccountPayload;
|
||||
import bisq.core.payment.payload.PaymentAccountPayload;
|
||||
import bisq.core.payment.payload.PaymentMethod;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public final class AdvancedCashAccount extends PaymentAccount {
|
||||
public AdvancedCashAccount() {
|
||||
super(PaymentMethod.ADVANCED_CASH);
|
||||
tradeCurrencies.addAll(CurrencyUtil.getAllAdvancedCashCurrencies());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PaymentAccountPayload createPayload() {
|
||||
return new AdvancedCashAccountPayload(paymentMethod.getId(), id);
|
||||
}
|
||||
|
||||
public void setAccountNr(String accountNr) {
|
||||
((AdvancedCashAccountPayload) paymentAccountPayload).setAccountNr(accountNr);
|
||||
}
|
||||
|
||||
public String getAccountNr() {
|
||||
return ((AdvancedCashAccountPayload) paymentAccountPayload).getAccountNr();
|
||||
}
|
||||
}
|
@ -78,6 +78,8 @@ public class PaymentAccountFactory {
|
||||
return new F2FAccount();
|
||||
case PaymentMethod.PROMPT_PAY_ID:
|
||||
return new PromptPayAccount();
|
||||
case PaymentMethod.ADVANCED_CASH_ID:
|
||||
return new AdvancedCashAccount();
|
||||
default:
|
||||
throw new RuntimeException("Not supported PaymentMethod: " + paymentMethod);
|
||||
}
|
||||
|
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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.core.payment.payload;
|
||||
|
||||
import io.bisq.generated.protobuffer.PB;
|
||||
|
||||
import com.google.protobuf.Message;
|
||||
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString
|
||||
@Setter
|
||||
@Getter
|
||||
@Slf4j
|
||||
public final class AdvancedCashAccountPayload extends PaymentAccountPayload {
|
||||
private String accountNr = "";
|
||||
|
||||
public AdvancedCashAccountPayload(String paymentMethod, String id) {
|
||||
super(paymentMethod, id);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private AdvancedCashAccountPayload(String paymentMethod,
|
||||
String id,
|
||||
String accountNr,
|
||||
long maxTradePeriod,
|
||||
@Nullable Map<String, String> excludeFromJsonDataMap) {
|
||||
super(paymentMethod,
|
||||
id,
|
||||
maxTradePeriod,
|
||||
excludeFromJsonDataMap);
|
||||
|
||||
this.accountNr = accountNr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message toProtoMessage() {
|
||||
return getPaymentAccountPayloadBuilder()
|
||||
.setAdvancedCashAccountPayload(PB.AdvancedCashAccountPayload.newBuilder()
|
||||
.setAccountNr(accountNr))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static AdvancedCashAccountPayload fromProto(PB.PaymentAccountPayload proto) {
|
||||
return new AdvancedCashAccountPayload(proto.getPaymentMethodId(),
|
||||
proto.getId(),
|
||||
proto.getAdvancedCashAccountPayload().getAccountNr(),
|
||||
proto.getMaxTradePeriod(),
|
||||
CollectionUtils.isEmpty(proto.getExcludeFromJsonDataMap()) ? null : new HashMap<>(proto.getExcludeFromJsonDataMap()));
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public String getPaymentDetails() {
|
||||
return "Advanced Cash - Wallet ID: " + accountNr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPaymentDetailsForTradePopup() {
|
||||
return getPaymentDetails();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getAgeWitnessInputData() {
|
||||
return super.getAgeWitnessInputData(accountNr.getBytes(Charset.forName("UTF-8")));
|
||||
}
|
||||
}
|
@ -82,6 +82,7 @@ public final class PaymentMethod implements PersistablePayload, Comparable {
|
||||
public static final String F2F_ID = "F2F";
|
||||
public static final String BLOCK_CHAINS_ID = "BLOCK_CHAINS";
|
||||
public static final String PROMPT_PAY_ID = "PROMPT_PAY";
|
||||
public static final String ADVANCED_CASH_ID = "ADVANCED_CASH";
|
||||
|
||||
@Deprecated
|
||||
public static PaymentMethod OK_PAY;
|
||||
@ -114,6 +115,7 @@ public final class PaymentMethod implements PersistablePayload, Comparable {
|
||||
public static PaymentMethod HAL_CASH;
|
||||
public static PaymentMethod BLOCK_CHAINS;
|
||||
public static PaymentMethod PROMPT_PAY;
|
||||
public static PaymentMethod ADVANCED_CASH;
|
||||
|
||||
private static List<PaymentMethod> ALL_VALUES;
|
||||
|
||||
@ -224,6 +226,7 @@ public final class PaymentMethod implements PersistablePayload, Comparable {
|
||||
UPHOLD = new PaymentMethod(UPHOLD_ID, DAY, maxTradeLimitHighRisk),
|
||||
REVOLUT = new PaymentMethod(REVOLUT_ID, DAY, maxTradeLimitHighRisk),
|
||||
PERFECT_MONEY = new PaymentMethod(PERFECT_MONEY_ID, DAY, maxTradeLimitLowRisk),
|
||||
ADVANCED_CASH = new PaymentMethod(ADVANCED_CASH_ID, DAY, maxTradeLimitVeryLowRisk),
|
||||
|
||||
// China
|
||||
ALI_PAY = new PaymentMethod(ALI_PAY_ID, DAY, maxTradeLimitLowRisk),
|
||||
|
@ -20,6 +20,7 @@ package bisq.core.proto;
|
||||
import bisq.core.dao.governance.blindvote.storage.BlindVotePayload;
|
||||
import bisq.core.dao.governance.proposal.storage.appendonly.ProposalPayload;
|
||||
import bisq.core.payment.AccountAgeWitness;
|
||||
import bisq.core.payment.payload.AdvancedCashAccountPayload;
|
||||
import bisq.core.payment.payload.AliPayAccountPayload;
|
||||
import bisq.core.payment.payload.CashAppAccountPayload;
|
||||
import bisq.core.payment.payload.CashDepositAccountPayload;
|
||||
@ -138,6 +139,8 @@ public class CoreProtoResolver implements ProtoResolver {
|
||||
return USPostalMoneyOrderAccountPayload.fromProto(proto);
|
||||
case PROMPT_PAY_ACCOUNT_PAYLOAD:
|
||||
return PromptPayAccountPayload.fromProto(proto);
|
||||
case ADVANCED_CASH_ACCOUNT_PAYLOAD:
|
||||
return AdvancedCashAccountPayload.fromProto(proto);
|
||||
default:
|
||||
throw new ProtobufferRuntimeException("Unknown proto message case(PB.PaymentAccountPayload). messageCase=" + messageCase);
|
||||
}
|
||||
|
@ -2450,6 +2450,8 @@ HAL_CASH=HalCash
|
||||
BLOCK_CHAINS=Altcoins
|
||||
# suppress inspection "UnusedProperty"
|
||||
PROMPT_PAY=PromptPay
|
||||
# suppress inspection "UnusedProperty"
|
||||
ADVANCED_CASH=Advanced Cash
|
||||
|
||||
# suppress inspection "UnusedProperty"
|
||||
OK_PAY_SHORT=OKPay
|
||||
@ -2491,7 +2493,8 @@ HAL_CASH_SHORT=HalCash
|
||||
BLOCK_CHAINS_SHORT=Altcoins
|
||||
# suppress inspection "UnusedProperty"
|
||||
PROMPT_PAY_SHORT=PromptPay
|
||||
|
||||
# suppress inspection "UnusedProperty"
|
||||
ADVANCED_CASH_SHORT=Advanced Cash
|
||||
|
||||
####################################################################
|
||||
# Validation
|
||||
@ -2552,3 +2555,4 @@ validation.amountBelowDust=The amount below the dust limit of {0} is not allowed
|
||||
validation.length=Length must be between {0} and {1}
|
||||
validation.pattern=Input must be of format: {0}
|
||||
validation.noHexString=The input is not in HEX format.
|
||||
validation.advancedCash.invalidFormat=Must be a valid email or wallet id of format: X000000000000
|
||||
|
137
core/src/test/java/bisq/core/app/BisqHelpFormatterTest.java
Normal file
137
core/src/test/java/bisq/core/app/BisqHelpFormatterTest.java
Normal file
@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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.core.app;
|
||||
|
||||
import joptsimple.OptionParser;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class BisqHelpFormatterTest {
|
||||
|
||||
@Test
|
||||
public void testHelpFormatter() throws IOException, URISyntaxException {
|
||||
|
||||
OptionParser parser = new OptionParser();
|
||||
|
||||
parser.formatHelpWith(new BisqHelpFormatter("Bisq Test", "bisq-test", "0.1.0"));
|
||||
|
||||
parser.accepts("name",
|
||||
"The name of the Bisq node")
|
||||
.withRequiredArg()
|
||||
.ofType(String.class)
|
||||
.defaultsTo("Bisq");
|
||||
|
||||
parser.accepts("another-option",
|
||||
"This is a long description which will need to break over multiple linessssssssssss such " +
|
||||
"that no line is longer than 80 characters in the help output.")
|
||||
.withRequiredArg()
|
||||
.ofType(String.class)
|
||||
.defaultsTo("WAT");
|
||||
|
||||
parser.accepts("exactly-72-char-description",
|
||||
"012345678911234567892123456789312345678941234567895123456789612345678971")
|
||||
.withRequiredArg()
|
||||
.ofType(String.class);
|
||||
|
||||
parser.accepts("exactly-72-char-description-with-spaces",
|
||||
" 123456789 123456789 123456789 123456789 123456789 123456789 123456789 1")
|
||||
.withRequiredArg()
|
||||
.ofType(String.class);
|
||||
|
||||
parser.accepts("90-char-description-without-spaces",
|
||||
"-123456789-223456789-323456789-423456789-523456789-623456789-723456789-823456789-923456789")
|
||||
.withRequiredArg()
|
||||
.ofType(String.class);
|
||||
|
||||
parser.accepts("90-char-description-with-space-at-char-80",
|
||||
"-123456789-223456789-323456789-423456789-523456789-623456789-723456789-823456789 923456789")
|
||||
.withRequiredArg()
|
||||
.ofType(String.class);
|
||||
|
||||
parser.accepts("90-char-description-with-spaces-at-chars-5-and-80",
|
||||
"-123 56789-223456789-323456789-423456789-523456789-623456789-723456789-823456789 923456789")
|
||||
.withRequiredArg()
|
||||
.ofType(String.class);
|
||||
|
||||
parser.accepts("90-char-description-with-space-at-char-73",
|
||||
"-123456789-223456789-323456789-423456789-523456789-623456789-723456789-8 3456789-923456789")
|
||||
.withRequiredArg()
|
||||
.ofType(String.class);
|
||||
|
||||
parser.accepts("1-char-description-with-only-a-space", " ")
|
||||
.withRequiredArg()
|
||||
.ofType(String.class);
|
||||
|
||||
parser.accepts("empty-description", "")
|
||||
.withRequiredArg()
|
||||
.ofType(String.class);
|
||||
|
||||
parser.accepts("no-description")
|
||||
.withRequiredArg()
|
||||
.ofType(String.class);
|
||||
|
||||
parser.accepts("no-arg", "Some description");
|
||||
|
||||
parser.accepts("optional-arg",
|
||||
"Option description")
|
||||
.withOptionalArg();
|
||||
|
||||
parser.accepts("with-default-value",
|
||||
"Some option with a default value")
|
||||
.withRequiredArg()
|
||||
.ofType(String.class)
|
||||
.defaultsTo("Wat");
|
||||
|
||||
parser.accepts("data-dir",
|
||||
"Application data directory")
|
||||
.withRequiredArg()
|
||||
.ofType(File.class)
|
||||
.defaultsTo(new File("/Users/cbeams/Library/Application Support/Bisq"));
|
||||
|
||||
parser.accepts("enum-opt",
|
||||
"Some option that accepts an enum value as an argument")
|
||||
.withRequiredArg()
|
||||
.ofType(AnEnum.class)
|
||||
.defaultsTo(AnEnum.foo);
|
||||
|
||||
ByteArrayOutputStream actual = new ByteArrayOutputStream();
|
||||
String expected = new String(Files.readAllBytes(Paths.get(getClass().getResource("cli-output.txt").toURI())));
|
||||
if (System.getProperty("os.name").startsWith("Windows")) {
|
||||
expected = new String(Files.readAllBytes(Paths.get(getClass().getResource("cli-output_windows.txt").toURI())));
|
||||
}
|
||||
|
||||
parser.printHelpOn(new PrintStream(actual));
|
||||
assertThat(actual.toString(), equalTo(expected));
|
||||
}
|
||||
|
||||
|
||||
enum AnEnum {foo, bar, baz}
|
||||
}
|
57
core/src/test/resources/bisq/core/app/cli-output.txt
Normal file
57
core/src/test/resources/bisq/core/app/cli-output.txt
Normal file
@ -0,0 +1,57 @@
|
||||
Bisq Test version 0.1.0
|
||||
|
||||
Usage: bisq-test [options]
|
||||
|
||||
Options:
|
||||
|
||||
--name=<String> (default: Bisq)
|
||||
The name of the Bisq node
|
||||
|
||||
--another-option=<String> (default: WAT)
|
||||
This is a long description which will need to break over multiple
|
||||
linessssssssssss such that no line is longer than 80 characters in the
|
||||
help output.
|
||||
|
||||
--exactly-72-char-description=<String>
|
||||
012345678911234567892123456789312345678941234567895123456789612345678971
|
||||
|
||||
--exactly-72-char-description-with-spaces=<String>
|
||||
123456789 123456789 123456789 123456789 123456789 123456789 123456789 1
|
||||
|
||||
--90-char-description-without-spaces=<String>
|
||||
-123456789-223456789-323456789-423456789-523456789-623456789-723456789-823456789-923456789
|
||||
|
||||
--90-char-description-with-space-at-char-80=<String>
|
||||
-123456789-223456789-323456789-423456789-523456789-623456789-723456789-823456789
|
||||
923456789
|
||||
|
||||
--90-char-description-with-spaces-at-chars-5-and-80=<String>
|
||||
-123
|
||||
56789-223456789-323456789-423456789-523456789-623456789-723456789-823456789
|
||||
923456789
|
||||
|
||||
--90-char-description-with-space-at-char-73=<String>
|
||||
-123456789-223456789-323456789-423456789-523456789-623456789-723456789-8
|
||||
3456789-923456789
|
||||
|
||||
--1-char-description-with-only-a-space=<String>
|
||||
|
||||
--empty-description=<String>
|
||||
|
||||
--no-description=<String>
|
||||
|
||||
--no-arg
|
||||
Some description
|
||||
|
||||
--optional-arg=<value>
|
||||
Option description
|
||||
|
||||
--with-default-value=<String> (default: Wat)
|
||||
Some option with a default value
|
||||
|
||||
--data-dir=<File> (default: /Users/cbeams/Library/Application Support/Bisq)
|
||||
Application data directory
|
||||
|
||||
--enum-opt=<foo|bar|baz> (default: foo)
|
||||
Some option that accepts an enum value as an argument
|
||||
|
57
core/src/test/resources/bisq/core/app/cli-output_windows.txt
Normal file
57
core/src/test/resources/bisq/core/app/cli-output_windows.txt
Normal file
@ -0,0 +1,57 @@
|
||||
Bisq Test version 0.1.0
|
||||
|
||||
Usage: bisq-test [options]
|
||||
|
||||
Options:
|
||||
|
||||
--name=<String> (default: Bisq)
|
||||
The name of the Bisq node
|
||||
|
||||
--another-option=<String> (default: WAT)
|
||||
This is a long description which will need to break over multiple
|
||||
linessssssssssss such that no line is longer than 80 characters in the
|
||||
help output.
|
||||
|
||||
--exactly-72-char-description=<String>
|
||||
012345678911234567892123456789312345678941234567895123456789612345678971
|
||||
|
||||
--exactly-72-char-description-with-spaces=<String>
|
||||
123456789 123456789 123456789 123456789 123456789 123456789 123456789 1
|
||||
|
||||
--90-char-description-without-spaces=<String>
|
||||
-123456789-223456789-323456789-423456789-523456789-623456789-723456789-823456789-923456789
|
||||
|
||||
--90-char-description-with-space-at-char-80=<String>
|
||||
-123456789-223456789-323456789-423456789-523456789-623456789-723456789-823456789
|
||||
923456789
|
||||
|
||||
--90-char-description-with-spaces-at-chars-5-and-80=<String>
|
||||
-123
|
||||
56789-223456789-323456789-423456789-523456789-623456789-723456789-823456789
|
||||
923456789
|
||||
|
||||
--90-char-description-with-space-at-char-73=<String>
|
||||
-123456789-223456789-323456789-423456789-523456789-623456789-723456789-8
|
||||
3456789-923456789
|
||||
|
||||
--1-char-description-with-only-a-space=<String>
|
||||
|
||||
--empty-description=<String>
|
||||
|
||||
--no-description=<String>
|
||||
|
||||
--no-arg
|
||||
Some description
|
||||
|
||||
--optional-arg=<value>
|
||||
Option description
|
||||
|
||||
--with-default-value=<String> (default: Wat)
|
||||
Some option with a default value
|
||||
|
||||
--data-dir=<File> (default: \Users\cbeams\Library\Application Support\Bisq)
|
||||
Application data directory
|
||||
|
||||
--enum-opt=<foo|bar|baz> (default: foo)
|
||||
Some option that accepts an enum value as an argument
|
||||
|
@ -13,7 +13,7 @@ sudo apt-get dist-upgrade
|
||||
if [ ! -f "${JAVA_HOME}/jre/lib/security/local_policy.jar" ]; then
|
||||
echo "Enabling strong crypto support for Java.."
|
||||
|
||||
wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jce/8/jce_policy-8.zip
|
||||
wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie" https://download.oracle.com/otn-pub/java/jce/8/jce_policy-8.zip
|
||||
|
||||
checksum=f3020a3922efd6626c2fff45695d527f34a8020e938a49292561f18ad1320b59 # see https://github.com/jonathancross/jc-docs/blob/master/java-strong-crypto-test/README.md
|
||||
|
||||
|
@ -25,6 +25,7 @@ import bisq.core.app.BisqExecutable;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.app.AppModule;
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.proto.persistable.PersistedDataHost;
|
||||
import bisq.common.setup.CommonSetup;
|
||||
|
||||
@ -39,6 +40,10 @@ import lombok.extern.slf4j.Slf4j;
|
||||
public class BisqAppMain extends BisqExecutable {
|
||||
private BisqApp application;
|
||||
|
||||
public BisqAppMain() {
|
||||
super("Bisq Desktop", "bisq-desktop", Version.VERSION);
|
||||
}
|
||||
|
||||
/* @Nullable
|
||||
private BisqHttpApiServer bisqHttpApiServer;*/
|
||||
/* @Nullable
|
||||
|
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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.components.paymentmethods;
|
||||
|
||||
import bisq.desktop.components.InputTextField;
|
||||
import bisq.desktop.util.FormBuilder;
|
||||
import bisq.desktop.util.Layout;
|
||||
import bisq.desktop.util.validation.AdvancedCashValidator;
|
||||
|
||||
import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.payment.AccountAgeWitnessService;
|
||||
import bisq.core.payment.AdvancedCashAccount;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.payment.payload.AdvancedCashAccountPayload;
|
||||
import bisq.core.payment.payload.PaymentAccountPayload;
|
||||
import bisq.core.util.BSFormatter;
|
||||
import bisq.core.util.validation.InputValidator;
|
||||
|
||||
import bisq.common.util.Tuple2;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.VPos;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static bisq.desktop.util.FormBuilder.*;
|
||||
|
||||
@Deprecated
|
||||
public class AdvancedCashForm extends PaymentMethodForm {
|
||||
private static final Logger log = LoggerFactory.getLogger(AdvancedCashForm.class);
|
||||
|
||||
private final AdvancedCashAccount advancedCashAccount;
|
||||
private final AdvancedCashValidator advancedCashValidator;
|
||||
private InputTextField accountNrInputTextField;
|
||||
|
||||
public static int addFormForBuyer(GridPane gridPane, int gridRow,
|
||||
PaymentAccountPayload paymentAccountPayload) {
|
||||
addCompactTopLabelTextFieldWithCopyIcon(gridPane, ++gridRow, Res.get("payment.wallet"),
|
||||
((AdvancedCashAccountPayload) paymentAccountPayload).getAccountNr());
|
||||
return gridRow;
|
||||
}
|
||||
|
||||
public AdvancedCashForm(PaymentAccount paymentAccount, AccountAgeWitnessService accountAgeWitnessService, AdvancedCashValidator advancedCashValidator,
|
||||
InputValidator inputValidator, GridPane gridPane, int gridRow, BSFormatter formatter) {
|
||||
super(paymentAccount, accountAgeWitnessService, inputValidator, gridPane, gridRow, formatter);
|
||||
this.advancedCashAccount = (AdvancedCashAccount) paymentAccount;
|
||||
this.advancedCashValidator = advancedCashValidator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFormForAddAccount() {
|
||||
gridRowFrom = gridRow + 1;
|
||||
|
||||
accountNrInputTextField = FormBuilder.addInputTextField(gridPane, ++gridRow, Res.get("payment.wallet"));
|
||||
accountNrInputTextField.setValidator(advancedCashValidator);
|
||||
accountNrInputTextField.textProperty().addListener((ov, oldValue, newValue) -> {
|
||||
advancedCashAccount.setAccountNr(newValue);
|
||||
updateFromInputs();
|
||||
});
|
||||
|
||||
addCurrenciesGrid(true);
|
||||
addLimitations(false);
|
||||
addAccountNameTextFieldWithAutoFillToggleButton();
|
||||
}
|
||||
|
||||
private void addCurrenciesGrid(boolean isEditable) {
|
||||
final Tuple2<Label, FlowPane> labelFlowPaneTuple2 = addTopLabelFlowPane(gridPane, ++gridRow, Res.get("payment.supportedCurrencies"), 0);
|
||||
|
||||
FlowPane flowPane = labelFlowPaneTuple2.second;
|
||||
|
||||
if (isEditable)
|
||||
flowPane.setId("flow-pane-checkboxes-bg");
|
||||
else
|
||||
flowPane.setId("flow-pane-checkboxes-non-editable-bg");
|
||||
|
||||
CurrencyUtil.getAllAdvancedCashCurrencies().stream().forEach(e ->
|
||||
fillUpFlowPaneWithCurrencies(isEditable, flowPane, e, advancedCashAccount));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void autoFillNameTextField() {
|
||||
if (useCustomAccountNameToggleButton != null && !useCustomAccountNameToggleButton.isSelected()) {
|
||||
String accountNr = accountNrInputTextField.getText();
|
||||
accountNr = StringUtils.abbreviate(accountNr, 9);
|
||||
String method = Res.get(paymentAccount.getPaymentMethod().getId());
|
||||
accountNameTextField.setText(method.concat(": ").concat(accountNr));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFormForDisplayAccount() {
|
||||
gridRowFrom = gridRow;
|
||||
addCompactTopLabelTextField(gridPane, gridRow, Res.get("payment.account.name"),
|
||||
advancedCashAccount.getAccountName(), Layout.FIRST_ROW_AND_GROUP_DISTANCE);
|
||||
addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("shared.paymentMethod"),
|
||||
Res.get(advancedCashAccount.getPaymentMethod().getId()));
|
||||
addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("payment.wallet"),
|
||||
advancedCashAccount.getAccountNr());
|
||||
addLimitations(true);
|
||||
addCurrenciesGrid(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAllInputsValid() {
|
||||
allInputsValid.set(isAccountNameValid()
|
||||
&& advancedCashValidator.validate(advancedCashAccount.getAccountNr()).isValid
|
||||
&& advancedCashAccount.getTradeCurrencies().size() > 0);
|
||||
}
|
||||
|
||||
}
|
@ -19,6 +19,7 @@ package bisq.desktop.main.account.content.fiataccounts;
|
||||
|
||||
import bisq.desktop.common.view.FxmlView;
|
||||
import bisq.desktop.components.TitledGroupBg;
|
||||
import bisq.desktop.components.paymentmethods.AdvancedCashForm;
|
||||
import bisq.desktop.components.paymentmethods.AliPayForm;
|
||||
import bisq.desktop.components.paymentmethods.CashAppForm;
|
||||
import bisq.desktop.components.paymentmethods.CashDepositForm;
|
||||
@ -52,6 +53,7 @@ import bisq.desktop.main.overlays.popups.Popup;
|
||||
import bisq.desktop.util.FormBuilder;
|
||||
import bisq.desktop.util.GUIUtil;
|
||||
import bisq.desktop.util.Layout;
|
||||
import bisq.desktop.util.validation.AdvancedCashValidator;
|
||||
import bisq.desktop.util.validation.AliPayValidator;
|
||||
import bisq.desktop.util.validation.BICValidator;
|
||||
import bisq.desktop.util.validation.CashAppValidator;
|
||||
@ -141,6 +143,7 @@ public class FiatAccountsView extends PaymentAccountsView<GridPane, FiatAccounts
|
||||
private final HalCashValidator halCashValidator;
|
||||
private final F2FValidator f2FValidator;
|
||||
private final PromptPayValidator promptPayValidator;
|
||||
private final AdvancedCashValidator advancedCashValidator;
|
||||
private final AccountAgeWitnessService accountAgeWitnessService;
|
||||
private final BSFormatter formatter;
|
||||
private ComboBox<PaymentMethod> paymentMethodComboBox;
|
||||
@ -172,6 +175,7 @@ public class FiatAccountsView extends PaymentAccountsView<GridPane, FiatAccounts
|
||||
HalCashValidator halCashValidator,
|
||||
F2FValidator f2FValidator,
|
||||
PromptPayValidator promptPayValidator,
|
||||
AdvancedCashValidator advancedCashValidator,
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
BSFormatter formatter) {
|
||||
super(model);
|
||||
@ -197,6 +201,7 @@ public class FiatAccountsView extends PaymentAccountsView<GridPane, FiatAccounts
|
||||
this.halCashValidator = halCashValidator;
|
||||
this.f2FValidator = f2FValidator;
|
||||
this.promptPayValidator = promptPayValidator;
|
||||
this.advancedCashValidator = advancedCashValidator;
|
||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
this.formatter = formatter;
|
||||
}
|
||||
@ -465,6 +470,8 @@ public class FiatAccountsView extends PaymentAccountsView<GridPane, FiatAccounts
|
||||
return new F2FForm(paymentAccount, accountAgeWitnessService, f2FValidator, inputValidator, root, gridRow, formatter);
|
||||
case PaymentMethod.PROMPT_PAY_ID:
|
||||
return new PromptPayForm(paymentAccount, accountAgeWitnessService, promptPayValidator, inputValidator, root, gridRow, formatter);
|
||||
case PaymentMethod.ADVANCED_CASH_ID:
|
||||
return new AdvancedCashForm(paymentAccount, accountAgeWitnessService, advancedCashValidator, inputValidator, root, gridRow, formatter);
|
||||
default:
|
||||
log.error("Not supported PaymentMethod: " + paymentMethod);
|
||||
return null;
|
||||
|
@ -20,6 +20,7 @@ package bisq.desktop.main.portfolio.pendingtrades.steps.buyer;
|
||||
import bisq.desktop.components.BusyAnimation;
|
||||
import bisq.desktop.components.TextFieldWithCopyIcon;
|
||||
import bisq.desktop.components.TitledGroupBg;
|
||||
import bisq.desktop.components.paymentmethods.AdvancedCashForm;
|
||||
import bisq.desktop.components.paymentmethods.AliPayForm;
|
||||
import bisq.desktop.components.paymentmethods.CashAppForm;
|
||||
import bisq.desktop.components.paymentmethods.CashDepositForm;
|
||||
@ -299,6 +300,9 @@ public class BuyerStep2View extends TradeStepView {
|
||||
case PaymentMethod.PROMPT_PAY_ID:
|
||||
gridRow = PromptPayForm.addFormForBuyer(gridPane, gridRow, paymentAccountPayload);
|
||||
break;
|
||||
case PaymentMethod.ADVANCED_CASH_ID:
|
||||
gridRow = AdvancedCashForm.addFormForBuyer(gridPane, gridRow, paymentAccountPayload);
|
||||
break;
|
||||
default:
|
||||
log.error("Not supported PaymentMethod: " + paymentMethodId);
|
||||
}
|
||||
|
@ -0,0 +1,36 @@
|
||||
package bisq.desktop.util.validation;
|
||||
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.util.validation.InputValidator;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class AdvancedCashValidator extends InputValidator {
|
||||
private EmailValidator emailValidator;
|
||||
private RegexValidator regexValidator;
|
||||
|
||||
@Inject
|
||||
public AdvancedCashValidator(EmailValidator emailValidator, RegexValidator regexValidator) {
|
||||
|
||||
this.emailValidator = emailValidator;
|
||||
|
||||
regexValidator.setPattern("[A-Za-z]{1}\\d{12}");
|
||||
regexValidator.setErrorMessage(Res.get("validation.advancedCash.invalidFormat"));
|
||||
this.regexValidator = regexValidator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationResult validate(String input) {
|
||||
ValidationResult result = super.validate(input);
|
||||
|
||||
if (!result.isValid)
|
||||
return result;
|
||||
|
||||
result = emailValidator.validate(input);
|
||||
|
||||
if (!result.isValid)
|
||||
result = regexValidator.validate(input);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package bisq.desktop.util.validation;
|
||||
|
||||
import bisq.core.app.BisqEnvironment;
|
||||
import bisq.core.btc.BaseCurrencyNetwork;
|
||||
import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.locale.Res;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class AdvancedCashValidatorTest {
|
||||
@Before
|
||||
public void setup() {
|
||||
final BaseCurrencyNetwork baseCurrencyNetwork = BisqEnvironment.getBaseCurrencyNetwork();
|
||||
final String currencyCode = baseCurrencyNetwork.getCurrencyCode();
|
||||
Res.setBaseCurrencyCode(currencyCode);
|
||||
Res.setBaseCurrencyName(baseCurrencyNetwork.getCurrencyName());
|
||||
CurrencyUtil.setBaseCurrencyCode(currencyCode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validate(){
|
||||
AdvancedCashValidator validator = new AdvancedCashValidator(
|
||||
new EmailValidator(),
|
||||
new RegexValidator()
|
||||
);
|
||||
|
||||
assertTrue(validator.validate("U123456789012").isValid);
|
||||
assertTrue(validator.validate("test@user.com").isValid);
|
||||
|
||||
assertFalse(validator.validate("").isValid);
|
||||
assertFalse(validator.validate(null).isValid);
|
||||
assertFalse(validator.validate("123456789012").isValid);
|
||||
}
|
||||
}
|
@ -15,12 +15,16 @@ You do _not_ need to install Gradle to complete the following command. The `grad
|
||||
|
||||
./gradlew build
|
||||
|
||||
If on Windows use the `gradlew.bat` script instead.
|
||||
|
||||
|
||||
## Run
|
||||
|
||||
The Bisq executable jar is now available in the `desktop/build/libs/` directory. Run it as follows, replacing `{version}` with the actual version found in the filename:
|
||||
Bisq executables are now available in the root project directory. Run Bisq Desktop as follows:
|
||||
|
||||
java -jar desktop/build/libs/desktop-{version}-all.jar
|
||||
./bisq-desktop
|
||||
|
||||
If on Windows use the `bisq-desktop.bat` script instead.
|
||||
|
||||
|
||||
## See also
|
||||
|
@ -61,14 +61,14 @@ Here is an overview:
|
||||
|
||||
## Run Bisq seednode
|
||||
|
||||
For localhost/regtest mode run the `SeedNodeMain.java` class or the `seednode.jar` (inside the `seednode/build/libs` folder) with following program arguments:
|
||||
For localhost/regtest mode run the `SeedNodeMain` class or `./bisq-seednode` script in the root project dir with following program arguments:
|
||||
|
||||
--baseCurrencyNetwork=BTC_REGTEST --useLocalhostForP2P=true --useDevPrivilegeKeys=true --nodePort=2002 --myAddress=localhost:2002 --appName=bisq-BTC_REGTEST_Seed_2002
|
||||
|
||||
|
||||
### Run Bisq arbitrator instance
|
||||
|
||||
For localhost/regtest mode run the `BisqAppMain.java` class or the `desktop.jar` (inside the `desktop/build/libs` folder) with following program arguments:
|
||||
For localhost/regtest mode run the `BisqAppMain` class or `./bisq-desktop` script in the root project dir with following program arguments:
|
||||
|
||||
--baseCurrencyNetwork=BTC_REGTEST --useLocalhostForP2P=true --useDevPrivilegeKeys=true --nodePort=4444 --appName=bisq-BTC_REGTEST_arbitrator
|
||||
|
||||
@ -79,7 +79,7 @@ _Note: You need only register once but if you have shut down all nodes (includin
|
||||
|
||||
### Run two Bisq trade instances
|
||||
|
||||
For localhost/regtest mode run the `BisqAppMain.java` class or the `desktop.jar` (inside the `desktop/build/libs` folder) with following program arguments:
|
||||
For localhost/regtest mode run the `BisqAppMain` class or `./bisq-desktop` script in the root project dir with following program arguments:
|
||||
|
||||
--baseCurrencyNetwork=BTC_REGTEST --useLocalhostForP2P=true --useDevPrivilegeKeys=true --nodePort=5555 --appName=bisq-BTC_REGTEST_Alice
|
||||
|
||||
|
@ -42,6 +42,10 @@ public class MonitorMain extends ExecutableForAppWithP2p {
|
||||
private static final String VERSION = "1.0.1";
|
||||
private Monitor monitor;
|
||||
|
||||
public MonitorMain() {
|
||||
super("Bisq Monitor", "bisq-monitor", VERSION);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
log.info("Monitor.VERSION: " + VERSION);
|
||||
BisqEnvironment.setDefaultAppName("bisq_monitor");
|
||||
@ -119,16 +123,20 @@ public class MonitorMain extends ExecutableForAppWithP2p {
|
||||
super.customizeOptionParsing(parser);
|
||||
|
||||
parser.accepts(MonitorOptionKeys.SLACK_URL_SEED_CHANNEL,
|
||||
description("Set slack secret for seed node monitor", ""))
|
||||
"Set slack secret for seed node monitor")
|
||||
.withRequiredArg();
|
||||
|
||||
parser.accepts(MonitorOptionKeys.SLACK_BTC_SEED_CHANNEL,
|
||||
description("Set slack secret for Btc node monitor", ""))
|
||||
"Set slack secret for Btc node monitor")
|
||||
.withRequiredArg();
|
||||
|
||||
parser.accepts(MonitorOptionKeys.SLACK_PROVIDER_SEED_CHANNEL,
|
||||
description("Set slack secret for provider node monitor", ""))
|
||||
"Set slack secret for provider node monitor")
|
||||
.withRequiredArg();
|
||||
|
||||
parser.accepts(MonitorOptionKeys.PORT,
|
||||
description("Set port to listen on", "80"))
|
||||
.withRequiredArg();
|
||||
"Set port to listen on")
|
||||
.withRequiredArg()
|
||||
.defaultsTo("80");
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ public class NetworkNodeProvider implements Provider<NetworkNode> {
|
||||
@Named(NetworkOptionKeys.EXTERNAL_TOR_USE_SAFECOOKIE) boolean useSafeCookieAuthentication ) {
|
||||
networkNode = useLocalhostForP2P ?
|
||||
new LocalhostNetworkNode(address, port, networkProtoResolver) :
|
||||
new TorNetworkNode(port, torDir, networkProtoResolver,
|
||||
new TorNetworkNode(port, networkProtoResolver,
|
||||
!controlPort.isEmpty() ?
|
||||
new RunningTor(torDir, Integer.parseInt(controlPort), password, cookieFile, useSafeCookieAuthentication) :
|
||||
new NewTor(torDir, torrcFile, torrcOptions, bridgeAddressProvider.getBridgeAddresses()));
|
||||
|
@ -30,8 +30,8 @@ import org.berndpruenster.netlayer.tor.NativeTor;
|
||||
import org.berndpruenster.netlayer.tor.Tor;
|
||||
import org.berndpruenster.netlayer.tor.TorCtlException;
|
||||
import org.berndpruenster.netlayer.tor.Torrc;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* This class creates a brand new instance of the Tor onion router.
|
||||
@ -44,20 +44,27 @@ import org.slf4j.LoggerFactory;
|
||||
* @author Florian Reimair
|
||||
*
|
||||
*/
|
||||
@Slf4j
|
||||
public class NewTor extends TorMode {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(NewTor.class);
|
||||
/**
|
||||
* <code>Netlayer</code> stores its hidden service files in a custom
|
||||
* subdirectory of <code>$torDir/hiddenservice/</code>. Note that the
|
||||
* {@link HiddenServiceSocket} does add this part on its own, hence,
|
||||
* {@link NewTor#getHiddenServiceDirectory()} returns only the custom
|
||||
* subdirectory (which happens to be <code>""</code>)
|
||||
*/
|
||||
private static final String HIDDEN_SERVICE_DIRECTORY = "hiddenservice";
|
||||
|
||||
private final String torrcFile;
|
||||
private final String torrcOptions;
|
||||
private final Collection<String> bridgeEntries;
|
||||
private final File torWorkikngDirectory;
|
||||
|
||||
public NewTor(File torWorkingDirectory, String torrcFile, String torrcOptions, Collection<String> bridgeEntries) {
|
||||
super(torWorkingDirectory, HIDDEN_SERVICE_DIRECTORY);
|
||||
this.torrcFile = torrcFile;
|
||||
this.torrcOptions = torrcOptions;
|
||||
this.bridgeEntries = bridgeEntries;
|
||||
this.torWorkikngDirectory = torWorkingDirectory;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -103,7 +110,7 @@ public class NewTor extends TorMode {
|
||||
override = new Torrc(torrcOptionsMap);
|
||||
|
||||
log.info("Starting tor");
|
||||
NativeTor result = new NativeTor(torWorkikngDirectory, bridgeEntries, override);
|
||||
NativeTor result = new NativeTor(torDir, bridgeEntries, override);
|
||||
log.info(
|
||||
"\n################################################################\n"
|
||||
+ "Tor started after {} ms. Start publishing hidden service.\n"
|
||||
|
@ -24,8 +24,8 @@ import java.util.Date;
|
||||
import org.berndpruenster.netlayer.tor.ExternalTor;
|
||||
import org.berndpruenster.netlayer.tor.Tor;
|
||||
import org.berndpruenster.netlayer.tor.TorCtlException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* This class creates a brand new instance of the Tor onion router.
|
||||
@ -37,19 +37,19 @@ import org.slf4j.LoggerFactory;
|
||||
* @author Florian Reimair
|
||||
*
|
||||
*/
|
||||
@Slf4j
|
||||
public class RunningTor extends TorMode {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(RunningTor.class);
|
||||
private static final String EXTERNAL_TOR_HIDDEN_SERVICE = "externalTorHiddenService";
|
||||
private final int controlPort;
|
||||
private final String password;
|
||||
private final String torDir;
|
||||
private final File cookieFile;
|
||||
private final boolean useSafeCookieAuthentication;
|
||||
|
||||
|
||||
public RunningTor(final File torDir, final int controlPort, final String password, final String cookieFile,
|
||||
final boolean useSafeCookieAuthentication) {
|
||||
this.torDir = torDir.getAbsolutePath();
|
||||
super(torDir, EXTERNAL_TOR_HIDDEN_SERVICE);
|
||||
this.controlPort = controlPort;
|
||||
this.password = password;
|
||||
this.cookieFile = new File(cookieFile);
|
||||
@ -72,7 +72,7 @@ public class RunningTor extends TorMode {
|
||||
|
||||
log.info(
|
||||
"\n################################################################\n"
|
||||
+ "Tor started after {} ms. Start publishing hidden service.\n"
|
||||
+ "Connecting to Tor successful after {} ms. Start publishing hidden service.\n"
|
||||
+ "################################################################",
|
||||
(new Date().getTime() - ts1)); // takes usually a few seconds
|
||||
|
||||
@ -81,7 +81,7 @@ public class RunningTor extends TorMode {
|
||||
|
||||
@Override
|
||||
public String getHiddenServiceDirectory() {
|
||||
return torDir + File.separator + "externalTorHiddenService";
|
||||
return new File(torDir, EXTERNAL_TOR_HIDDEN_SERVICE).getAbsolutePath();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,6 +23,8 @@ import java.io.IOException;
|
||||
import org.berndpruenster.netlayer.tor.Tor;
|
||||
import org.berndpruenster.netlayer.tor.TorCtlException;
|
||||
|
||||
import bisq.common.storage.FileUtil;
|
||||
|
||||
/**
|
||||
* Holds information on how tor should be created and delivers a respective
|
||||
* {@link Tor} object when asked.
|
||||
@ -32,11 +34,31 @@ import org.berndpruenster.netlayer.tor.TorCtlException;
|
||||
*/
|
||||
public abstract class TorMode {
|
||||
|
||||
/**
|
||||
* The directory where the <code>private_key</code> file sits in. Kept private,
|
||||
* because it is only valid for the {@link TorMode#doRollingBackup()} due to the
|
||||
* inner workings of the <code>Netlayer</code> dependency.
|
||||
*/
|
||||
private final File hiddenServiceDirectory;
|
||||
protected final File torDir;
|
||||
|
||||
/**
|
||||
* @param torDir points to the place, where we will persist private
|
||||
* key and address data
|
||||
* @param hiddenServiceDir The directory where the <code>private_key</code> file
|
||||
* sits in. Note that, due to the inner workings of the
|
||||
* <code>Netlayer</code> dependency, it does not
|
||||
* necessarily equal
|
||||
* {@link TorMode#getHiddenServiceDirectory()}.
|
||||
*/
|
||||
public TorMode(File torDir, String hiddenServiceDir) {
|
||||
this.torDir = torDir;
|
||||
this.hiddenServiceDirectory = new File(torDir, hiddenServiceDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a fresh {@link Tor} object.
|
||||
*
|
||||
* @param torDir points to the place, where we will persist private key and
|
||||
* address data
|
||||
* @return a fresh instance of {@link Tor}
|
||||
* @throws IOException
|
||||
* @throws TorCtlException
|
||||
@ -49,12 +71,20 @@ public abstract class TorMode {
|
||||
* <code>""</code>) as a hidden service directory is fine. {@link ExternalTor},
|
||||
* however, does not have a Tor installation path and thus, takes the hidden
|
||||
* service path literally. Hence, we set
|
||||
* <code>"torDir/ephemeralHiddenService"</code> as the hidden service directory.
|
||||
* <code>"torDir/externalTorHiddenService"</code> as the hidden service
|
||||
* directory.
|
||||
*
|
||||
* @return <code>""</code> in {@link NewTor} Mode,
|
||||
* <code>"torDir/ephemeralHiddenService"</code> in {@link RunningTor}
|
||||
* <code>"torDir/externalTorHiddenService"</code> in {@link RunningTor}
|
||||
* mode
|
||||
*/
|
||||
public abstract String getHiddenServiceDirectory();
|
||||
|
||||
/**
|
||||
* Do a rolling backup of the "private_key" file.
|
||||
*/
|
||||
protected void doRollingBackup() {
|
||||
FileUtil.rollingBackup(hiddenServiceDirectory, "private_key", 20);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ import bisq.common.Timer;
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.app.Log;
|
||||
import bisq.common.proto.network.NetworkProtoResolver;
|
||||
import bisq.common.storage.FileUtil;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import org.berndpruenster.netlayer.tor.HiddenServiceSocket;
|
||||
@ -47,14 +46,9 @@ import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
||||
import java.net.Socket;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -74,7 +68,6 @@ public class TorNetworkNode extends NetworkNode {
|
||||
|
||||
|
||||
private HiddenServiceSocket hiddenServiceSocket;
|
||||
private final File torDir;
|
||||
private Timer shutDownTimeoutTimer;
|
||||
private int restartCounter;
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
@ -87,9 +80,8 @@ public class TorNetworkNode extends NetworkNode {
|
||||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public TorNetworkNode(int servicePort, File torDir, NetworkProtoResolver networkProtoResolver, TorMode torMode) {
|
||||
public TorNetworkNode(int servicePort, NetworkProtoResolver networkProtoResolver, TorMode torMode) {
|
||||
super(servicePort, networkProtoResolver);
|
||||
this.torDir = torDir;
|
||||
this.torMode = torMode;
|
||||
}
|
||||
|
||||
@ -100,8 +92,7 @@ public class TorNetworkNode extends NetworkNode {
|
||||
|
||||
@Override
|
||||
public void start(@Nullable SetupListener setupListener) {
|
||||
final File hiddenservice = new File(Paths.get(torDir.getAbsolutePath(), "hiddenservice").toString());
|
||||
FileUtil.rollingBackup(hiddenservice, "private_key", 20);
|
||||
torMode.doRollingBackup();
|
||||
|
||||
if (setupListener != null)
|
||||
addSetupListener(setupListener);
|
||||
@ -280,8 +271,8 @@ public class TorNetworkNode extends NetworkNode {
|
||||
log.error("Could not connect to running Tor: "
|
||||
+ e.getMessage());
|
||||
|
||||
// Seems a bit harsh, but since we cannot connect to Tor, we cannot do nothing
|
||||
// furthermore, we have no hidden services started yet, so there is no graceful
|
||||
// Seems a bit harsh, but since we cannot connect to Tor, we cannot do nothing.
|
||||
// Furthermore, we have no hidden services started yet, so there is no graceful
|
||||
// shutdown needed either
|
||||
System.exit(1);
|
||||
} catch (Throwable ignore) {
|
||||
|
@ -53,8 +53,7 @@ public class TorNetworkNodeTest {
|
||||
public void testTorNodeBeforeSecondReady() throws InterruptedException, IOException {
|
||||
latch = new CountDownLatch(1);
|
||||
int port = 9001;
|
||||
TorNetworkNode node1 = new TorNetworkNode(port, new File("torNode_" + port),
|
||||
TestUtils.getNetworkProtoResolver(),
|
||||
TorNetworkNode node1 = new TorNetworkNode(port, TestUtils.getNetworkProtoResolver(),
|
||||
new NewTor(new File("torNode_" + port), "", "", new ArrayList<String>()));
|
||||
node1.start(new SetupListener() {
|
||||
@Override
|
||||
@ -81,8 +80,7 @@ public class TorNetworkNodeTest {
|
||||
|
||||
latch = new CountDownLatch(1);
|
||||
int port2 = 9002;
|
||||
TorNetworkNode node2 = new TorNetworkNode(port2, new File("torNode_" + port2),
|
||||
TestUtils.getNetworkProtoResolver(),
|
||||
TorNetworkNode node2 = new TorNetworkNode(port2, TestUtils.getNetworkProtoResolver(),
|
||||
new NewTor(new File("torNode_" + port), "", "", new ArrayList<String>()));
|
||||
node2.start(new SetupListener() {
|
||||
@Override
|
||||
@ -140,8 +138,7 @@ public class TorNetworkNodeTest {
|
||||
public void testTorNodeAfterBothReady() throws InterruptedException, IOException {
|
||||
latch = new CountDownLatch(2);
|
||||
int port = 9001;
|
||||
TorNetworkNode node1 = new TorNetworkNode(port, new File("torNode_" + port),
|
||||
TestUtils.getNetworkProtoResolver(),
|
||||
TorNetworkNode node1 = new TorNetworkNode(port, TestUtils.getNetworkProtoResolver(),
|
||||
new NewTor(new File("torNode_" + port), "", "", new ArrayList<String>()));
|
||||
node1.start(new SetupListener() {
|
||||
@Override
|
||||
@ -167,8 +164,7 @@ public class TorNetworkNodeTest {
|
||||
});
|
||||
|
||||
int port2 = 9002;
|
||||
TorNetworkNode node2 = new TorNetworkNode(port2, new File("torNode_" + port),
|
||||
TestUtils.getNetworkProtoResolver(),
|
||||
TorNetworkNode node2 = new TorNetworkNode(port2, TestUtils.getNetworkProtoResolver(),
|
||||
new NewTor(new File("torNode_" + port), "", "", new ArrayList<String>()));
|
||||
node2.start(new SetupListener() {
|
||||
@Override
|
||||
|
@ -36,6 +36,10 @@ public class SeedNodeMain extends ExecutableForAppWithP2p {
|
||||
private static final String VERSION = "0.8.0";
|
||||
private SeedNode seedNode;
|
||||
|
||||
public SeedNodeMain() {
|
||||
super("Bisq Seednode", "bisq-seednode", VERSION);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
log.info("SeedNode.VERSION: " + VERSION);
|
||||
BisqEnvironment.setDefaultAppName("bisq_seednode");
|
||||
|
@ -35,6 +35,10 @@ public class StatisticsMain extends ExecutableForAppWithP2p {
|
||||
private static final String VERSION = "0.6.1";
|
||||
private Statistics statistics;
|
||||
|
||||
public StatisticsMain() {
|
||||
super("Bisq Statsnode", "bisq-statistics", VERSION);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
log.info("Statistics.VERSION: " + VERSION);
|
||||
BisqEnvironment.setDefaultAppName("bisq_statistics");
|
||||
|
Loading…
Reference in New Issue
Block a user