This commit is contained in:
mcneb10 2024-05-16 04:02:53 +00:00 committed by GitHub
commit 2a325f0cf7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 234 additions and 47 deletions

View File

@ -1,6 +1,7 @@
package haveno.common.config;
import ch.qos.logback.classic.Level;
import com.google.gson.Gson;
import joptsimple.AbstractOptionSpec;
import joptsimple.ArgumentAcceptingOptionSpec;
import joptsimple.HelpFormatter;
@ -20,6 +21,7 @@ import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@ -45,6 +47,7 @@ import static java.util.stream.Collectors.toList;
* when adding or modifying options. Furthermore, while accessor methods are often useful
* when mocking an object in a testing context, this class is designed for testability
* without needing to be mocked. See {@code ConfigTests} for examples.
*
* @see #Config(String...)
* @see #Config(String, File, String...)
*/
@ -135,6 +138,12 @@ public class Config {
public final File defaultAppDataDir;
public final File defaultConfigFile;
// Public key list
public final List<String> privateNotificationPublicKeys;
public final List<String> filterPublicKeys;
public final List<String> arbitratorPublicKeys;
public final List<String> alertPublicKeys;
// Options supported only at the command-line interface (cli)
public final boolean helpRequested;
public final File configFile;
@ -167,7 +176,7 @@ public class Config {
public final boolean dumpStatistics;
public final boolean ignoreDevMsg;
public final List<String> providers;
public final List<String> seedNodes;
public final List<String> seedNodes = new ArrayList<>();
public final List<String> banList;
public final boolean useLocalhostForP2P;
public final int maxConnections;
@ -203,6 +212,12 @@ public class Config {
public final boolean bypassMempoolValidation;
public final boolean passwordRequired;
public final double makerFeePct;
public final double takerFeePct;
public final double penaltyFeePct;
public final boolean arbitratorAssignsTradeFeeAddress;
// Properties derived from options but not exposed as options themselves
public final File torDir;
public final File walletDir;
@ -220,6 +235,7 @@ public class Config {
* to the actual system user data directory and/or real Haveno application data
* directory. Most production use cases will favor calling the
* {@link #Config(String, File, String...)} constructor directly.
*
* @param args zero or more command line arguments in the form "--optName=optValue"
* @throws ConfigException if any problems are encountered during option parsing
* @see #Config(String, File, String...)
@ -240,9 +256,10 @@ public class Config {
* will take precedence. Note that the {@value HELP} and {@value CONFIG_FILE} options
* are supported only at the command line and are disallowed within the config file
* itself.
* @param defaultAppName typically "Haveno" or similar
*
* @param defaultAppName typically "Haveno" or similar
* @param defaultUserDataDir typically the OS-specific user data directory location
* @param args zero or more command line arguments in the form "--optName=optValue"
* @param args zero or more command line arguments in the form "--optName=optValue"
* @throws ConfigException if any problems are encountered during option parsing
*/
public Config(String defaultAppName, File defaultUserDataDir, String... args) {
@ -257,7 +274,7 @@ public class Config {
ArgumentAcceptingOptionSpec<String> configFileOpt =
parser.accepts(CONFIG_FILE, format("Specify configuration file. " +
"Relative paths will be prefixed by %s location.", APP_DATA_DIR))
"Relative paths will be prefixed by %s location.", APP_DATA_DIR))
.withRequiredArg()
.ofType(String.class)
.defaultsTo(DEFAULT_CONFIG_FILE_NAME);
@ -336,7 +353,7 @@ public class Config {
ArgumentAcceptingOptionSpec<Boolean> ignoreLocalXmrNodeOpt = // TODO: update this to ignore local XMR node
parser.accepts(IGNORE_LOCAL_XMR_NODE,
"If set to true a Monero node running locally will be ignored")
"If set to true a Monero node running locally will be ignored")
.withRequiredArg()
.ofType(Boolean.class)
.defaultsTo(false);
@ -356,21 +373,21 @@ public class Config {
ArgumentAcceptingOptionSpec<Boolean> useDevModeOpt =
parser.accepts(USE_DEV_MODE,
"Enables dev mode which is used for convenience for developer testing")
"Enables dev mode which is used for convenience for developer testing")
.withRequiredArg()
.ofType(boolean.class)
.defaultsTo(false);
ArgumentAcceptingOptionSpec<Boolean> useDevModeHeaderOpt =
parser.accepts(USE_DEV_MODE_HEADER,
"Use dev mode css scheme to distinguish dev instances.")
"Use dev mode css scheme to distinguish dev instances.")
.withRequiredArg()
.ofType(boolean.class)
.defaultsTo(false);
ArgumentAcceptingOptionSpec<Boolean> useDevPrivilegeKeysOpt =
parser.accepts(USE_DEV_PRIVILEGE_KEYS, "If set to true all privileged features requiring a private " +
"key to be enabled are overridden by a dev key pair (This is for developers only!)")
"key to be enabled are overridden by a dev key pair (This is for developers only!)")
.withRequiredArg()
.ofType(boolean.class)
.defaultsTo(false);
@ -383,9 +400,9 @@ public class Config {
ArgumentAcceptingOptionSpec<Boolean> ignoreDevMsgOpt =
parser.accepts(IGNORE_DEV_MSG, "If set to true all signed " +
"network_messages from haveno developers are ignored (Global " +
"alert, Version update alert, Filters for offers, nodes or " +
"trading account data)")
"network_messages from haveno developers are ignored (Global " +
"alert, Version update alert, Filters for offers, nodes or " +
"trading account data)")
.withRequiredArg()
.ofType(boolean.class)
.defaultsTo(false);
@ -398,7 +415,7 @@ public class Config {
ArgumentAcceptingOptionSpec<String> seedNodesOpt =
parser.accepts(SEED_NODES, "Override hard coded seed nodes as comma separated list e.g. " +
"'rxdkppp3vicnbgqt.onion:8002,mfla72c4igh5ta2t.onion:8002'")
"'rxdkppp3vicnbgqt.onion:8002,mfla72c4igh5ta2t.onion:8002'")
.withRequiredArg()
.withValuesSeparatedBy(',')
.describedAs("host:port[,...]");
@ -430,22 +447,22 @@ public class Config {
ArgumentAcceptingOptionSpec<String> socks5ProxyHttpAddressOpt =
parser.accepts(SOCKS_5_PROXY_HTTP_ADDRESS,
"A proxy address to be used for Http requests (should be non-Tor)")
"A proxy address to be used for Http requests (should be non-Tor)")
.withRequiredArg()
.describedAs("host:port")
.defaultsTo("");
ArgumentAcceptingOptionSpec<Path> torrcFileOpt =
parser.accepts(TORRC_FILE, "An existing torrc-file to be sourced for Tor. Note that torrc-entries, " +
"which are critical to Haveno's correct operation, cannot be overwritten.")
"which are critical to Haveno's correct operation, cannot be overwritten.")
.withRequiredArg()
.describedAs("File")
.withValuesConvertedBy(new PathConverter(PathProperties.FILE_EXISTING, PathProperties.READABLE));
ArgumentAcceptingOptionSpec<String> torrcOptionsOpt =
parser.accepts(TORRC_OPTIONS, "A list of torrc-entries to amend to Haveno's torrc. Note that " +
"torrc-entries, which are critical to Haveno's flawless operation, cannot be overwritten. " +
"[torrc options line, torrc option, ...]")
"torrc-entries, which are critical to Haveno's flawless operation, cannot be overwritten. " +
"[torrc options line, torrc option, ...]")
.withRequiredArg()
.withValuesConvertedBy(RegexMatcher.regex("^([^\\s,]+\\s[^,]+,?\\s*)+$"))
.defaultsTo("");
@ -457,7 +474,7 @@ public class Config {
ArgumentAcceptingOptionSpec<Integer> torControlPortOpt =
parser.accepts(TOR_CONTROL_PORT,
"The control port of an already running Tor service to be used by Haveno.")
"The control port of an already running Tor service to be used by Haveno.")
.availableUnless(TORRC_FILE, TORRC_OPTIONS)
.withRequiredArg()
.ofType(int.class)
@ -472,7 +489,7 @@ public class Config {
ArgumentAcceptingOptionSpec<Path> torControlCookieFileOpt =
parser.accepts(TOR_CONTROL_COOKIE_FILE, "The cookie file for authenticating against the already " +
"running Tor service. Use in conjunction with --" + TOR_CONTROL_USE_SAFE_COOKIE_AUTH)
"running Tor service. Use in conjunction with --" + TOR_CONTROL_USE_SAFE_COOKIE_AUTH)
.availableIf(TOR_CONTROL_PORT)
.availableUnless(TOR_CONTROL_PASSWORD)
.withRequiredArg()
@ -481,7 +498,7 @@ public class Config {
OptionSpecBuilder torControlUseSafeCookieAuthOpt =
parser.accepts(TOR_CONTROL_USE_SAFE_COOKIE_AUTH,
"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(TOR_CONTROL_COOKIE_FILE);
OptionSpecBuilder torStreamIsolationOpt =
@ -550,21 +567,21 @@ public class Config {
ArgumentAcceptingOptionSpec<String> socks5DiscoverModeOpt =
parser.accepts(SOCKS5_DISCOVER_MODE, "Specify discovery mode for Bitcoin nodes. " +
"One or more of: [ADDR, DNS, ONION, ALL] (comma separated, they get OR'd together).")
"One or more of: [ADDR, DNS, ONION, ALL] (comma separated, they get OR'd together).")
.withRequiredArg()
.describedAs("mode[,...]")
.defaultsTo("ALL");
ArgumentAcceptingOptionSpec<Boolean> useAllProvidedNodesOpt =
parser.accepts(USE_ALL_PROVIDED_NODES,
"Set to true if connection of bitcoin nodes should include clear net nodes")
"Set to true if connection of bitcoin nodes should include clear net nodes")
.withRequiredArg()
.ofType(boolean.class)
.defaultsTo(false);
ArgumentAcceptingOptionSpec<String> userAgentOpt =
parser.accepts(USER_AGENT,
"User agent at btc node connections")
"User agent at btc node connections")
.withRequiredArg()
.defaultsTo("Haveno");
@ -587,28 +604,28 @@ public class Config {
ArgumentAcceptingOptionSpec<Boolean> preventPeriodicShutdownAtSeedNodeOpt =
parser.accepts(PREVENT_PERIODIC_SHUTDOWN_AT_SEED_NODE,
"Prevents periodic shutdown at seed nodes")
"Prevents periodic shutdown at seed nodes")
.withRequiredArg()
.ofType(boolean.class)
.defaultsTo(false);
ArgumentAcceptingOptionSpec<Boolean> republishMailboxEntriesOpt =
parser.accepts(REPUBLISH_MAILBOX_ENTRIES,
"Republish mailbox messages at startup")
"Republish mailbox messages at startup")
.withRequiredArg()
.ofType(boolean.class)
.defaultsTo(false);
ArgumentAcceptingOptionSpec<Boolean> bypassMempoolValidationOpt =
parser.accepts(BYPASS_MEMPOOL_VALIDATION,
"Prevents mempool check of trade parameters")
"Prevents mempool check of trade parameters")
.withRequiredArg()
.ofType(boolean.class)
.defaultsTo(false);
ArgumentAcceptingOptionSpec<Boolean> passwordRequiredOpt =
parser.accepts(PASSWORD_REQUIRED,
"Requires a password for creating a Haveno account")
"Requires a password for creating a Haveno account")
.withRequiredArg()
.ofType(boolean.class)
.defaultsTo(false);
@ -696,7 +713,7 @@ public class Config {
this.dumpStatistics = options.valueOf(dumpStatisticsOpt);
this.ignoreDevMsg = options.valueOf(ignoreDevMsgOpt);
this.providers = options.valuesOf(providersOpt);
this.seedNodes = options.valuesOf(seedNodesOpt);
this.seedNodes.addAll(options.valuesOf(seedNodesOpt));
this.banList = options.valuesOf(banListOpt);
this.useLocalhostForP2P = !this.baseCurrencyNetwork.isMainnet() && options.valueOf(useLocalhostForP2POpt);
this.maxConnections = options.valueOf(maxConnectionsOpt);
@ -739,6 +756,31 @@ public class Config {
this.torDir = mkdir(btcNetworkDir, "tor");
this.walletDir = mkdir(btcNetworkDir, "wallet");
// Get network specific settings
File jsonFile = new File(btcNetworkDir.getAbsolutePath() + "/network.json");
try {
if(!jsonFile.exists()) {
jsonFile.createNewFile();
String json = new Gson().toJson(new NetworkJSONFile());
Files.writeString(jsonFile.toPath(), json);
}
NetworkJSONFile network_info = new Gson().fromJson(Files.readString(jsonFile.toPath()), NetworkJSONFile.class);
this.alertPublicKeys = network_info.getAlertKeys();
this.arbitratorPublicKeys = network_info.getArbitratorKeys();
this.filterPublicKeys = network_info.getFilterKeys();
this.privateNotificationPublicKeys = network_info.getPrivateNotificationKeys();
this.makerFeePct = network_info.getMaker_ratio();
this.takerFeePct = network_info.getTaker_ratio();
this.penaltyFeePct = network_info.getTaker_ratio();
this.arbitratorAssignsTradeFeeAddress = network_info.getArbitratorAssignsTradeFeeAddress();
for(NetworkJSONFile.NetworkJSONFileSeedNode seedNode : network_info.getSeedNodes()) {
seedNodes.add(seedNode.onionAddress);
}
} catch (IOException e) {
throw new ConfigException("Failed to process network.json file: ", e);
}
// Assign values to special-case static fields
APP_DATA_DIR_VALUE = appDataDir;
BASE_CURRENCY_NETWORK_VALUE = baseCurrencyNetwork;
@ -779,7 +821,6 @@ public class Config {
}
}
// == STATIC UTILS ===================================================================
private static String randomAppName() {
@ -804,6 +845,7 @@ public class Config {
/**
* Creates {@value APP_DATA_DIR} including any nonexistent parent directories. Does
* nothing if the directory already exists.
*
* @return the given directory, now guaranteed to exist
*/
private static File mkAppDataDir(File dir) {
@ -820,6 +862,7 @@ public class Config {
/**
* Creates child directory assuming parent directories already exist. Does nothing if
* the directory already exists.
*
* @return the child directory, now guaranteed to exist
*/
private static File mkdir(File parent, String child) {
@ -843,10 +886,11 @@ public class Config {
* because of its large number of subclasses, injecting the Guice-managed
* {@link Config} class is not worth the effort. {@link #appDataDir} should be
* favored in all other cases.
*
* @throws NullPointerException if the static value has not yet been assigned, i.e. if
* the Guice-managed {@link Config} class has not yet been instantiated elsewhere.
* This should never be the case, as Guice wiring always happens before any
* {@code Overlay} class is instantiated.
* the Guice-managed {@link Config} class has not yet been instantiated elsewhere.
* This should never be the case, as Guice wiring always happens before any
* {@code Overlay} class is instantiated.
*/
public static File appDataDir() {
return checkNotNull(APP_DATA_DIR_VALUE, "The static appDataDir has not yet " +
@ -873,6 +917,7 @@ public class Config {
* the <a href="https://en.wikipedia.org/wiki/Law_of_Demeter">Law of Demeter</a>. The
* non-static {@link #baseCurrencyNetwork} property should be favored whenever
* possible.
*
* @see #baseCurrencyNetwork()
*/
public static NetworkParameters baseCurrencyNetworkParameters() {

View File

@ -0,0 +1,113 @@
package haveno.common.config;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
public class NetworkJSONFile {
private List<String> alertKeys = new ArrayList<>();
private List<String> arbitratorKeys = new ArrayList<>();
private List<String> filterKeys = new ArrayList<>();
private List<String> privateNotificationKeys = new ArrayList<>();
private Boolean arbitratorAssignsTradeFeeAddress = true;
private Double maker_ratio = 0.0015;
private Double taker_ratio = 0.0075;
private Double penalty_ratio = 0.02;
/*
private static final boolean ARBITRATOR_ASSIGNS_TRADE_FEE_ADDRESS_DEFAULT = true;
private static final double TAKER_RATIO_DEFAULT = 0.0015;
private static final double MAKER_RATIO_DEFAULT = 0.0075;
private static final double PENALTY_RATIO_DEFAULT = 0.02;*/
private List<NetworkJSONFileSeedNode> seedNodes = new ArrayList<>();
public boolean getArbitratorAssignsTradeFeeAddress() {
return arbitratorAssignsTradeFeeAddress;
}
public void setArbitratorAssignsTradeFeeAddress(Boolean arbitratorAssignsTradeFeeAddress) {
this.arbitratorAssignsTradeFeeAddress = arbitratorAssignsTradeFeeAddress;
}
public double getTaker_ratio() {
return taker_ratio;
}
public void setTaker_ratio(Double taker_ratio) {
this.taker_ratio = taker_ratio;
}
public double getMaker_ratio() {
return maker_ratio;
}
public void setMaker_ratio(Double maker_ratio) {
this.maker_ratio = maker_ratio;
}
public double getPenalty_ratio() {
return penalty_ratio;
}
public void setPenalty_ratio(Double penalty_ratio) {
this.penalty_ratio = penalty_ratio;
}
public List<String> getAlertKeys() {
if(privateNotificationKeys == null || privateNotificationKeys.size() < 1) throw new NetworkJSONParseException("alertKeys needs to be a populated list of public keys!");
return alertKeys;
}
public void setAlertKeys(List<String> alertKeys) {
this.alertKeys = alertKeys;
}
public List<String> getArbitratorKeys() {
if(privateNotificationKeys == null || privateNotificationKeys.size() < 1) throw new NetworkJSONParseException("arbitratorKeys needs to be a populated list of public keys!");
return arbitratorKeys;
}
public void setArbitratorKeys(List<String> arbitratorKeys) {
this.arbitratorKeys = arbitratorKeys;
}
public List<String> getFilterKeys() {
if(privateNotificationKeys == null || privateNotificationKeys.size() < 1) throw new NetworkJSONParseException("filterKeys needs to be a populated list of public keys!");
return filterKeys;
}
public void setFilterKeys(List<String> filterKeys) {
this.filterKeys = filterKeys;
}
public List<String> getPrivateNotificationKeys() {
if(privateNotificationKeys == null || privateNotificationKeys.size() < 1) throw new NetworkJSONParseException("privateNotificationKeys needs to be a populated list of public keys!");
return privateNotificationKeys;
}
public void setPrivateNotificationKeys(List<String> privateNotificationKeys) {
this.privateNotificationKeys = privateNotificationKeys;
}
public List<NetworkJSONFileSeedNode> getSeedNodes() {
if(seedNodes == null || seedNodes.size() < 1) throw new NetworkJSONParseException("seedNodes needs to be a populated list of seed nodes!");
return seedNodes;
}
public void setSeedNodes(List<NetworkJSONFileSeedNode> seedNodes) {
this.seedNodes = seedNodes;
}
class NetworkJSONFileSeedNode {
String onionAddress;
String info;
}
class NetworkJSONParseException extends ConfigException {
public NetworkJSONParseException(String format, Object... args) {
super(format, args);
}
}
}

View File

@ -23,6 +23,7 @@ import com.google.inject.name.Named;
import haveno.common.app.DevEnv;
import haveno.common.config.Config;
import haveno.common.crypto.KeyRing;
import haveno.core.trade.HavenoUtils;
import haveno.core.user.User;
import haveno.network.p2p.P2PService;
import haveno.network.p2p.storage.HashMapChangedListener;
@ -109,7 +110,7 @@ public class AlertManager {
"026c581ad773d987e6bd10785ac7f7e0e64864aedeb8bce5af37046de812a37854",
"025b058c9f2c60d839669dbfa5578cf5a8117d60e6b70e2f0946f8a691273c6a36");
case XMR_MAINNET:
return List.of();
return HavenoUtils.havenoSetup.getConfig().alertPublicKeys;
default:
throw new RuntimeException("Unhandled base currency network: " + Config.baseCurrencyNetwork());
}

View File

@ -29,6 +29,7 @@ import haveno.common.config.Config;
import haveno.common.crypto.KeyRing;
import haveno.common.crypto.PubKeyRing;
import haveno.common.proto.network.NetworkEnvelope;
import haveno.core.trade.HavenoUtils;
import haveno.network.p2p.DecryptedMessageWithPubKey;
import haveno.network.p2p.NodeAddress;
import haveno.network.p2p.P2PService;
@ -108,7 +109,7 @@ public class PrivateNotificationManager implements MessageListener {
"026c581ad773d987e6bd10785ac7f7e0e64864aedeb8bce5af37046de812a37854",
"025b058c9f2c60d839669dbfa5578cf5a8117d60e6b70e2f0946f8a691273c6a36");
case XMR_MAINNET:
return List.of();
return HavenoUtils.havenoSetup.getConfig().privateNotificationPublicKeys;
default:
throw new RuntimeException("Unhandled base currency network: " + Config.baseCurrencyNetwork());
}

View File

@ -384,6 +384,8 @@ public class HavenoSetup {
p2PService.getP2PDataStorage().readFromResources(postFix, completeHandler);
}
public Config getConfig() {return config;}
private synchronized void resetStartupTimeout() {
if (p2pNetworkAndWalletInitialized != null && p2pNetworkAndWalletInitialized.get()) return; // skip if already initialized
if (startupTimeout != null) startupTimeout.stop();

View File

@ -116,9 +116,7 @@ public class FilterManager {
publicKeys = useDevPrivilegeKeys ?
Collections.singletonList(DevEnv.DEV_PRIVILEGE_PUB_KEY) :
List.of("0358d47858acdc41910325fce266571540681ef83a0d6fedce312bef9810793a27",
"029340c3e7d4bb0f9e651b5f590b434fecb6175aeaa57145c7804ff05d210e534f",
"034dc7530bf66ffd9580aa98031ea9a18ac2d269f7c56c0e71eca06105b9ed69f9");
config.filterPublicKeys;
banFilter.setBannedNodePredicate(this::isNodeAddressBannedFromNetwork);
}

View File

@ -189,9 +189,9 @@ public class CreateOfferService {
useMarketBasedPriceValue,
amountAsLong,
minAmountAsLong,
HavenoUtils.MAKER_FEE_PCT,
HavenoUtils.TAKER_FEE_PCT,
HavenoUtils.PENALTY_FEE_PCT,
HavenoUtils.havenoSetup.getConfig().makerFeePct,
HavenoUtils.havenoSetup.getConfig().takerFeePct,
HavenoUtils.havenoSetup.getConfig().penaltyFeePct,
securityDepositAsDouble,
securityDepositAsDouble,
baseCurrencyCode,

View File

@ -40,6 +40,7 @@ import haveno.common.config.Config;
import haveno.common.crypto.KeyRing;
import haveno.core.filter.FilterManager;
import haveno.core.support.dispute.agent.DisputeAgentManager;
import haveno.core.trade.HavenoUtils;
import haveno.core.user.User;
import haveno.network.p2p.storage.payload.ProtectedStorageEntry;
import java.util.ArrayList;
@ -49,7 +50,6 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
@Singleton
public class ArbitratorManager extends DisputeAgentManager<Arbitrator> {
@Inject
public ArbitratorManager(KeyRing keyRing,
ArbitratorService arbitratorService,
@ -79,7 +79,7 @@ public class ArbitratorManager extends DisputeAgentManager<Arbitrator> {
"02a1a458df5acf4ab08fdca748e28f33a955a30854c8c1a831ee733dca7f0d2fcd",
"0374dd70f3fa6e47ec5ab97932e1cec6233e98e6ae3129036b17118650c44fd3de");
case XMR_MAINNET:
return new ArrayList<String>();
return HavenoUtils.havenoSetup.getConfig().arbitratorPublicKeys;
default:
throw new RuntimeException("Unhandled base currency network: " + Config.baseCurrencyNetwork());
}

View File

@ -37,12 +37,39 @@ Run `./gradlew generateKeypairs`. A list of public/private keypairs will print t
For demonstration, we can use the first generated public/private keypair for all roles, but you can customize as desired.
Hardcode the public key(s) in these files:
The public keys are stored in the `network.json` file inside the appdata folder, which resolves to these locations by default with the appName `haveno-XMR_MAINNET_Seed_1002` would resolve to `~/.local/share/haveno-XMR_MAINNET_Seed_1002/xmr_mainnet/network.json`
Here is an example config:
- [AlertManager.java](https://github.com/haveno-dex/haveno/blob/1bf83ecb8baa06b6bfcc30720f165f20b8f77025/core/src/main/java/haveno/core/alert/AlertManager.java#L111)
- [ArbitratorManager.java](https://github.com/haveno-dex/haveno/blob/1bf83ecb8baa06b6bfcc30720f165f20b8f77025/core/src/main/java/haveno/core/support/dispute/arbitration/arbitrator/ArbitratorManager.java#L81)
- [FilterManager.java](https://github.com/haveno-dex/haveno/blob/1bf83ecb8baa06b6bfcc30720f165f20b8f77025/core/src/main/java/haveno/core/filter/FilterManager.java#L117)
- [PrivateNotificationManager.java](https://github.com/haveno-dex/haveno/blob/mainnet_placeholders/core/src/main/java/haveno/core/alert/PrivateNotificationManager.java#L110)
```json
{
"alertKeys": [
"03894c9184e7a59a55e3fc14cfeae6df446b4a216a401ae7fb59bc83bf340a0ac7"
],
"arbitratorKeys": [
"03894c9184e7a59a55e3fc14cfeae6df446b4a216a401ae7fb59bc83bf340a0ac7"
],
"filterKeys": [
"03894c9184e7a59a55e3fc14cfeae6df446b4a216a401ae7fb59bc83bf340a0ac7"
],
"privateNotificationKeys": [
"03894c9184e7a59a55e3fc14cfeae6df446b4a216a401ae7fb59bc83bf340a0ac7"
],
"arbitratorAssignsTradeFeeAddress": true,
"maker_ratio": 0.0025,
"taker_ratio": 0.0075,
"penalty_ratio": 0.02,
"seedNodes":[
{
"onionAddress": "example.onion:1002",
"info": "@nobody"
}
]
}
```
Fill in the JSON file with the network's settings or replace it with the provided `network.json`
## Change the default folder name for Haveno application data
@ -142,4 +169,4 @@ To build the installers for distribution, first change `XMR_STAGENET` to `XMR_MA
Then [follow instructions](https://github.com/haveno-dex/haveno/blob/master/desktop/package/README.md) to build the installers for distribution.
Alternatively, the installers are built automatically by GitHub.
Alternatively, the installers are built automatically by GitHub.