Merge branch 'release-candidate-0.9.0' into improve-backward-compatibility-with-new-arb-selection

This commit is contained in:
Manfred Karrer 2018-11-25 23:55:01 +01:00
commit 735e4588ce
No known key found for this signature in database
GPG Key ID: 401250966A6B2C46
35 changed files with 1128 additions and 159 deletions

3
.gitignore vendored
View File

@ -21,7 +21,8 @@ build
*.java.hsp
*.~ava
/bundles
/bisq*
/bisq-*
/lib
/xchange
desktop.ini
*/target/*

View File

@ -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')
}
}
}

View File

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

View File

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

View File

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

View 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());
}
}

View File

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

View File

@ -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"),

View File

@ -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();
}
}

View File

@ -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);
}

View File

@ -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")));
}
}

View File

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

View File

@ -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);
}

View File

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

View 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}
}

View 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

View 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

View File

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

View File

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

View File

@ -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);
}
}

View File

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

View File

@ -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);
}

View File

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

View File

@ -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);
}
}

View File

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

View File

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

View File

@ -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");
}
}

View File

@ -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()));

View File

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

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

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

View File

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

View File

@ -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");

View File

@ -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");