Normalize line endings, attempt 2
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
[package]
|
||||
name = "grin_wallet_config"
|
||||
version = "5.4.0-alpha.1"
|
||||
authors = ["Grin Developers <mimblewimble@lists.launchpad.net>"]
|
||||
description = "Configuration for grin wallet , a simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format."
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/mimblewimble/grin-wallet"
|
||||
keywords = [ "crypto", "grin", "mimblewimble" ]
|
||||
workspace = ".."
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
rand = "0.6"
|
||||
serde = "1"
|
||||
serde_derive = "1"
|
||||
toml = "0.5"
|
||||
dirs = "2.0"
|
||||
log = "0.4"
|
||||
|
||||
grin_wallet_util = { path = "../util", version = "5.4.0-alpha.1" }
|
||||
|
||||
##### Grin Imports
|
||||
|
||||
# For Release
|
||||
grin_core = "5.3.3"
|
||||
grin_util = "5.3.3"
|
||||
|
||||
# For beta release
|
||||
|
||||
#grin_core = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3"}
|
||||
#grin_util = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
|
||||
# For bleeding edge
|
||||
# grin_core = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
# grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
|
||||
# For local testing
|
||||
# grin_core = { path = "../../grin/core"}
|
||||
# grin_util = { path = "../../grin/util"}
|
||||
|
||||
#####
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.6"
|
||||
|
||||
@@ -0,0 +1,396 @@
|
||||
// Copyright 2021 The Grin Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Comments for configuration + injection into output .toml
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// maps entries to Comments that should precede them
|
||||
fn comments() -> HashMap<String, String> {
|
||||
let mut retval = HashMap::new();
|
||||
|
||||
retval.insert(
|
||||
"config_file_version".to_string(),
|
||||
"
|
||||
#Version of the Generated Configuration File for the Grin Wallet (DO NOT EDIT)
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
retval.insert(
|
||||
"[wallet]".to_string(),
|
||||
"
|
||||
#########################################
|
||||
### WALLET CONFIGURATION ###
|
||||
#########################################
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
retval.insert(
|
||||
"api_listen_port".to_string(),
|
||||
"
|
||||
#path of TLS certificate file, self-signed certificates are not supported
|
||||
#tls_certificate_file = \"\"
|
||||
#private key for the TLS certificate
|
||||
#tls_certificate_key = \"\"
|
||||
|
||||
#port for wallet listener
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
retval.insert(
|
||||
"owner_api_listen_port".to_string(),
|
||||
"
|
||||
#port for wallet owner api
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
retval.insert(
|
||||
"api_secret_path".to_string(),
|
||||
"
|
||||
#path of the secret token used by the API to authenticate the calls
|
||||
#comment it to disable basic auth
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
retval.insert(
|
||||
"check_node_api_http_addr".to_string(),
|
||||
"
|
||||
#where the wallet should find a running node
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
retval.insert(
|
||||
"node_api_secret_path".to_string(),
|
||||
"
|
||||
#location of the node api secret for basic auth on the Grin API
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
retval.insert(
|
||||
"owner_api_include_foreign".to_string(),
|
||||
"
|
||||
#include the foreign API endpoints on the same port as the owner
|
||||
#API. Useful for networking environments like AWS ECS that make
|
||||
#it difficult to access multiple ports on a single service.
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
retval.insert(
|
||||
"data_file_dir".to_string(),
|
||||
"
|
||||
#where to find wallet files (seed, data, etc)
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
retval.insert(
|
||||
"no_commit_cache".to_string(),
|
||||
"
|
||||
#If true, don't store calculated commits in the database
|
||||
#better privacy, but at a performance cost of having to
|
||||
#re-calculate commits every time they're used
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
retval.insert(
|
||||
"dark_background_color_scheme".to_string(),
|
||||
"
|
||||
#Whether to use the black background color scheme for command line
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
retval.insert(
|
||||
"accept_fee_base".to_string(),
|
||||
"
|
||||
#Minimum acceptable fee per unit of transaction weight
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
retval.insert(
|
||||
"[logging]".to_string(),
|
||||
"
|
||||
#Type of proxy, eg \"socks4\", \"socks5\", \"http\", \"https\"
|
||||
#transport = \"https\"
|
||||
|
||||
#Proxy address, eg IP:PORT or Hostname
|
||||
#server = \"\"
|
||||
|
||||
#Username for the proxy server authentification
|
||||
#user = \"\"
|
||||
|
||||
#Password for the proxy server authentification
|
||||
#pass = \"\"
|
||||
|
||||
#This computer goes through a firewall that only allows connections to certain ports (Optional)
|
||||
#allowed_port = [80, 443]
|
||||
|
||||
|
||||
#########################################
|
||||
### LOGGING CONFIGURATION ###
|
||||
#########################################
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
retval.insert(
|
||||
"log_to_stdout".to_string(),
|
||||
"
|
||||
#whether to log to stdout
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
retval.insert(
|
||||
"stdout_log_level".to_string(),
|
||||
"
|
||||
#log level for stdout: Error, Warning, Info, Debug, Trace
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
retval.insert(
|
||||
"log_to_file".to_string(),
|
||||
"
|
||||
#whether to log to a file
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
retval.insert(
|
||||
"file_log_level".to_string(),
|
||||
"
|
||||
#log level for file: Error, Warning, Info, Debug, Trace
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
retval.insert(
|
||||
"log_file_path".to_string(),
|
||||
"
|
||||
#log file path
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
retval.insert(
|
||||
"log_file_append".to_string(),
|
||||
"
|
||||
#whether to append to the log file (true), or replace it on every run (false)
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
retval.insert(
|
||||
"log_max_size".to_string(),
|
||||
"
|
||||
#maximum log file size in bytes before performing log rotation
|
||||
#comment it to disable log rotation
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
retval.insert(
|
||||
"[tor]".to_string(),
|
||||
"
|
||||
#########################################
|
||||
### TOR CONFIGURATION (Experimental) ###
|
||||
#########################################
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
retval.insert(
|
||||
"skip_send_attempt".to_string(),
|
||||
"
|
||||
#Whether to skip send attempts (used for debugging)
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
retval.insert(
|
||||
"use_tor_listener".to_string(),
|
||||
"
|
||||
#Whether to start tor listener on listener startup (default true)
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
retval.insert(
|
||||
"socks_proxy_addr".to_string(),
|
||||
"
|
||||
#Address of the running TOR (SOCKS) server
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
retval.insert(
|
||||
"send_config_dir".to_string(),
|
||||
"
|
||||
#Directory to output TOR configuration to when sending
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
retval.insert(
|
||||
"[tor.bridge]".to_string(),
|
||||
"
|
||||
#########################################
|
||||
### TOR BRIDGE ###
|
||||
#########################################
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
retval.insert(
|
||||
"[tor.proxy]".to_string(),
|
||||
"
|
||||
#Tor bridge relay: allow to send and receive via TOR in a country where it is censored.
|
||||
#Enable it by entering a single bridge line. To disable it, you must comment it.
|
||||
#Support of the transport: obfs4, meek and snowflake.
|
||||
#obfs4proxy or snowflake client binary must be installed and on your path.
|
||||
#For example, the bridge line must be in the following format for obfs4 transport: \"obfs4 [IP:PORT] [FINGERPRINT] cert=[CERT] iat-mode=[IAT-MODE]\"
|
||||
#bridge_line = \"\"
|
||||
|
||||
#Plugging client option, needed only for snowflake (let it empty if you want to use the default option of tor) or debugging purpose
|
||||
#client_option = \"\"
|
||||
|
||||
|
||||
#########################################
|
||||
### TOR PROXY ###
|
||||
#########################################
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
retval
|
||||
}
|
||||
|
||||
fn get_key(line: &str) -> String {
|
||||
if line.contains('[') && line.contains(']') {
|
||||
line.to_owned()
|
||||
} else if line.contains('=') {
|
||||
line.split('=').collect::<Vec<&str>>()[0].trim().to_owned()
|
||||
} else {
|
||||
"NOT_FOUND".to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_comments(orig: String) -> String {
|
||||
let comments = comments();
|
||||
let lines: Vec<&str> = orig.split('\n').collect();
|
||||
let mut out_lines = vec![];
|
||||
for l in lines {
|
||||
let key = get_key(l);
|
||||
if let Some(v) = comments.get(&key) {
|
||||
out_lines.push(v.to_owned());
|
||||
}
|
||||
out_lines.push(l.to_owned());
|
||||
out_lines.push("\n".to_owned());
|
||||
}
|
||||
let mut ret_val = String::from("");
|
||||
for l in out_lines {
|
||||
ret_val.push_str(&l);
|
||||
}
|
||||
ret_val
|
||||
}
|
||||
|
||||
pub fn migrate_comments(
|
||||
old_config: String,
|
||||
new_config: String,
|
||||
old_version: Option<u32>,
|
||||
) -> String {
|
||||
let comments = comments();
|
||||
// Prohibe the key we are basing on to introduce new comments for [tor.proxy]
|
||||
let prohibited_key = match old_version {
|
||||
None => vec!["[logging]"],
|
||||
Some(_) => vec![],
|
||||
};
|
||||
let mut vec_old_conf = vec![];
|
||||
let mut hm_key_cmt_old = HashMap::new();
|
||||
let old_conf: Vec<&str> = old_config.split_inclusive('\n').collect();
|
||||
// collect old key in a vec and insert old key/comments from the old conf in a hashmap
|
||||
let vec_key_old = old_conf
|
||||
.iter()
|
||||
.filter_map(|line| {
|
||||
let line_nospace = line.trim();
|
||||
let is_ascii_control = line_nospace.chars().all(|x| x.is_ascii_control());
|
||||
match line.contains("#") || is_ascii_control {
|
||||
true => {
|
||||
vec_old_conf.push(line.to_owned());
|
||||
None
|
||||
}
|
||||
false => {
|
||||
let comments: String = vec_old_conf.iter().flat_map(|s| s.chars()).collect();
|
||||
let key = get_key(line_nospace);
|
||||
match key != "NOT_FOUND" {
|
||||
true => {
|
||||
vec_old_conf.clear();
|
||||
hm_key_cmt_old.insert(key.clone(), comments);
|
||||
Some(key)
|
||||
}
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let new_conf: Vec<&str> = new_config.split_inclusive('\n').collect();
|
||||
// collect new key and the whole key line from the new config
|
||||
let vec_key_cmt_new = new_conf
|
||||
.iter()
|
||||
.filter_map(|line| {
|
||||
let line_nospace = line.trim();
|
||||
let is_ascii_control = line_nospace.chars().all(|x| x.is_ascii_control());
|
||||
match !line.contains("#") && !is_ascii_control {
|
||||
true => {
|
||||
let key = get_key(line_nospace);
|
||||
match key != "NOT_FOUND" {
|
||||
true => Some((key, line_nospace.to_string())),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
false => None,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<(String, String)>>();
|
||||
|
||||
let mut new_config_str = String::from("");
|
||||
// Merging old comments in the new config (except if the key is contained in the prohibited vec) with all new introduced key comments
|
||||
for (key, key_line) in vec_key_cmt_new {
|
||||
let old_key_exist = vec_key_old.iter().any(|old_key| *old_key == key);
|
||||
let key_fmt = format!("{}\n", key_line);
|
||||
if old_key_exist {
|
||||
if prohibited_key.contains(&key.as_str()) {
|
||||
// push new config key/comments
|
||||
let value = comments.get(&key).unwrap();
|
||||
new_config_str.push_str(value);
|
||||
new_config_str.push_str(&key_fmt);
|
||||
} else {
|
||||
// push old config key/comment
|
||||
let value = hm_key_cmt_old.get(&key).unwrap();
|
||||
new_config_str.push_str(value);
|
||||
new_config_str.push_str(&key_fmt);
|
||||
}
|
||||
} else {
|
||||
// old key does not exist, we push new key/comments
|
||||
let value = comments.get(&key).unwrap();
|
||||
new_config_str.push_str(value);
|
||||
new_config_str.push_str(&key_fmt);
|
||||
}
|
||||
}
|
||||
new_config_str
|
||||
}
|
||||
@@ -0,0 +1,478 @@
|
||||
// Copyright 2021 The Grin Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Configuration file management
|
||||
|
||||
use crate::comments::{insert_comments, migrate_comments};
|
||||
use crate::core::global;
|
||||
use crate::types::{
|
||||
ConfigError, GlobalWalletConfig, GlobalWalletConfigMembers, TorBridgeConfig, TorProxyConfig,
|
||||
};
|
||||
use crate::types::{TorConfig, WalletConfig};
|
||||
use crate::util::logger::LoggingConfig;
|
||||
use rand::distributions::{Alphanumeric, Distribution};
|
||||
use rand::thread_rng;
|
||||
use std::env;
|
||||
use std::fs::{self, File};
|
||||
use std::io::prelude::*;
|
||||
use std::io::BufReader;
|
||||
use std::path::{Path, PathBuf};
|
||||
use toml;
|
||||
|
||||
/// Wallet configuration file name
|
||||
pub const WALLET_CONFIG_FILE_NAME: &str = "grin-wallet.toml";
|
||||
const WALLET_LOG_FILE_NAME: &str = "grin-wallet.log";
|
||||
/// .grin folder, usually in home/.grin
|
||||
pub const GRIN_HOME: &str = ".grin";
|
||||
/// Wallet data directory
|
||||
pub const GRIN_WALLET_DIR: &str = "wallet_data";
|
||||
/// Node API secret
|
||||
pub const API_SECRET_FILE_NAME: &str = ".foreign_api_secret";
|
||||
/// Owner API secret
|
||||
pub const OWNER_API_SECRET_FILE_NAME: &str = ".owner_api_secret";
|
||||
|
||||
/// Function to locate the wallet dir and wallet.toml in the order
|
||||
/// a) config in top-dir if provided, b) in working dir, c) default dir
|
||||
/// Function to get wallet dir and create dirs if not existing
|
||||
pub fn get_wallet_path(
|
||||
chain_type: &global::ChainTypes,
|
||||
create_path: bool,
|
||||
) -> Result<PathBuf, ConfigError> {
|
||||
// A - Detect grin-wallet.toml in working dir
|
||||
let mut config_path = std::env::current_dir()?;
|
||||
config_path.push(WALLET_CONFIG_FILE_NAME);
|
||||
if create_path == false && config_path.exists() {
|
||||
config_path.pop();
|
||||
println!("Detected 'wallet.toml' in working dir - opening associated wallet");
|
||||
return Ok(config_path);
|
||||
};
|
||||
// B - Select home directory
|
||||
let mut wallet_path = match dirs::home_dir() {
|
||||
Some(p) => p,
|
||||
None => PathBuf::new(),
|
||||
};
|
||||
wallet_path.push(GRIN_HOME);
|
||||
wallet_path.push(chain_type.shortname());
|
||||
// Create if the default path doesn't exist
|
||||
if !wallet_path.exists() && create_path {
|
||||
fs::create_dir_all(wallet_path.clone())?;
|
||||
}
|
||||
// Throw an error if the path still does not exist
|
||||
if !wallet_path.exists() {
|
||||
Err(ConfigError::PathNotFoundError(String::from(
|
||||
wallet_path.to_str().unwrap(),
|
||||
)))
|
||||
} else {
|
||||
Ok(wallet_path)
|
||||
}
|
||||
}
|
||||
|
||||
/// Smart function to detect the the nodes .api_secret in the order
|
||||
/// a) top-dir, b) home directory, create directory if needed
|
||||
pub fn get_node_path(
|
||||
data_path: Option<PathBuf>,
|
||||
chain_type: &global::ChainTypes,
|
||||
) -> Result<PathBuf, ConfigError> {
|
||||
let node_path = match data_path {
|
||||
// 1) A If top dir provided and api_secret exist, return top dir
|
||||
Some(path) => {
|
||||
let mut node_path = path;
|
||||
node_path.push(GRIN_HOME);
|
||||
node_path.push(chain_type.shortname());
|
||||
node_path.push(API_SECRET_FILE_NAME);
|
||||
if node_path.exists() {
|
||||
node_path.pop();
|
||||
Ok(node_path)
|
||||
// 1) B If top dir exists, but no api_secret, return home dir
|
||||
} else {
|
||||
let mut node_path = match dirs::home_dir() {
|
||||
Some(p) => p,
|
||||
None => PathBuf::new(),
|
||||
};
|
||||
node_path.push(GRIN_HOME);
|
||||
node_path.push(chain_type.shortname());
|
||||
Ok(node_path)
|
||||
}
|
||||
}
|
||||
// 2) If there is no top_dir provided, always return home dir
|
||||
None => {
|
||||
let mut node_path = match dirs::home_dir() {
|
||||
Some(p) => p,
|
||||
None => PathBuf::new(),
|
||||
};
|
||||
node_path.push(GRIN_HOME);
|
||||
node_path.push(chain_type.shortname());
|
||||
Ok(node_path)
|
||||
}
|
||||
};
|
||||
node_path
|
||||
}
|
||||
|
||||
/// Checks if config in current working dir
|
||||
fn check_config_current_dir(path: &str) -> Option<PathBuf> {
|
||||
let p = env::current_dir();
|
||||
let mut c = match p {
|
||||
Ok(c) => c,
|
||||
Err(_) => {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
c.push(path);
|
||||
if c.exists() {
|
||||
return Some(c);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Whether a config file exists at the given directory
|
||||
pub fn config_file_exists(path: &str) -> bool {
|
||||
let mut path = PathBuf::from(path);
|
||||
path.push(WALLET_CONFIG_FILE_NAME);
|
||||
path.exists()
|
||||
}
|
||||
|
||||
/// Create file with api secret
|
||||
pub fn init_api_secret(api_secret_path: &PathBuf) -> Result<(), ConfigError> {
|
||||
let mut api_secret_file = File::create(api_secret_path)?;
|
||||
let api_secret: String = Alphanumeric
|
||||
.sample_iter(&mut thread_rng())
|
||||
.take(20)
|
||||
.collect();
|
||||
api_secret_file.write_all(api_secret.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if file contains a secret and nothing else
|
||||
pub fn check_api_secret(api_secret_path: &PathBuf) -> Result<(), ConfigError> {
|
||||
let api_secret_file = File::open(api_secret_path)?;
|
||||
let buf_reader = BufReader::new(api_secret_file);
|
||||
let mut lines_iter = buf_reader.lines();
|
||||
let first_line = lines_iter.next();
|
||||
if first_line.is_none() || first_line.unwrap().is_err() {
|
||||
fs::remove_file(api_secret_path)?;
|
||||
init_api_secret(api_secret_path)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check that the api secret file exists and is valid
|
||||
fn check_api_secret_file(
|
||||
chain_type: &global::ChainTypes,
|
||||
data_path: Option<PathBuf>,
|
||||
file_name: &str,
|
||||
) -> Result<(), ConfigError> {
|
||||
let grin_path = match data_path {
|
||||
Some(p) => p,
|
||||
None => get_node_path(data_path, chain_type)?,
|
||||
};
|
||||
let mut api_secret_path = grin_path;
|
||||
api_secret_path.push(file_name);
|
||||
if !api_secret_path.exists() {
|
||||
init_api_secret(&api_secret_path)
|
||||
} else {
|
||||
check_api_secret(&api_secret_path)
|
||||
}
|
||||
}
|
||||
|
||||
/// Initial wallet setup does the following
|
||||
/// 1) Load wallet config if run without 'init' 2) create wallet if run with 'init''
|
||||
/// Try in thiss order a) current dir as template, b) in top path, or c) .grin home
|
||||
/// - load default config values
|
||||
/// - update the wallet and node dir to the correct paths
|
||||
/// - if grin-wallet.toml exists, but the wallet data dir does not, load config and continue wallet generation
|
||||
/// - Automatically detect grin-wallet.toml in current directory
|
||||
pub fn initial_setup_wallet(
|
||||
chain_type: &global::ChainTypes,
|
||||
mut data_path: Option<PathBuf>,
|
||||
create_path: bool,
|
||||
) -> Result<GlobalWalletConfig, ConfigError> {
|
||||
// Fixing the input path when run with -here or -t (top-dir)
|
||||
// - Fix top-dir path to compensate for bug on Linux to handle "\"
|
||||
// - Convert top-dir path to be always absolute for config generation
|
||||
// - Fix for Windows 10/11 to strip the '\\?\' prefix added to the path
|
||||
if let Some(p) = &data_path {
|
||||
if let Some(p_str) = p.to_str() {
|
||||
let fixed_str = p_str.replace("\\", "/");
|
||||
let fixed_path = PathBuf::from(fixed_str);
|
||||
if create_path {
|
||||
fs::create_dir_all(&fixed_path)?;
|
||||
}
|
||||
let absolute_path = if fixed_path.is_absolute() {
|
||||
fixed_path.canonicalize()?
|
||||
} else {
|
||||
env::current_dir()?.join(&fixed_path).canonicalize()?
|
||||
};
|
||||
let absolute_path =
|
||||
std::path::PathBuf::from(absolute_path.to_str().unwrap().replace(r"\\?\", ""));
|
||||
data_path = Some(absolute_path); // Store the updated path
|
||||
}
|
||||
}
|
||||
|
||||
// Get wallet data_dir path if none provided
|
||||
let wallet_path = match data_path {
|
||||
Some(p) => p,
|
||||
None => get_wallet_path(chain_type, create_path)?,
|
||||
};
|
||||
println!("wallet path: {}", wallet_path.display());
|
||||
// Get path to the node directory,
|
||||
let node_path = get_node_path(Some(wallet_path.clone()), chain_type)?;
|
||||
|
||||
// Get config path and data path
|
||||
let mut config_path = wallet_path.clone();
|
||||
config_path.push(WALLET_CONFIG_FILE_NAME);
|
||||
let mut data_dir = wallet_path.clone();
|
||||
data_dir.push(GRIN_WALLET_DIR);
|
||||
// Check if a config exists in theworking dir, if so load it
|
||||
let (path, config) = match config_path.clone().exists() {
|
||||
// If the config does not exist, load default and updated node and wallet dir
|
||||
false => {
|
||||
let mut default_config = GlobalWalletConfig::for_chain(chain_type);
|
||||
default_config.config_file_path = Some(config_path.clone());
|
||||
default_config.update_paths(&wallet_path, &node_path);
|
||||
|
||||
// Write config file
|
||||
let res =
|
||||
default_config.write_to_file(config_path.to_str().unwrap(), false, None, None);
|
||||
|
||||
if let Err(e) = res {
|
||||
let msg = format!(
|
||||
"Error creating config file as ({}): {}",
|
||||
config_path.to_str().unwrap(),
|
||||
e
|
||||
);
|
||||
return Err(ConfigError::SerializationError(msg));
|
||||
}
|
||||
|
||||
(wallet_path, default_config)
|
||||
}
|
||||
|
||||
// Return config if not run with init
|
||||
true => {
|
||||
// If run with init and seed does not yet exists, continue, else throw error
|
||||
if data_dir.exists() && create_path == true {
|
||||
let msg = format!(
|
||||
"{} already exists in the target directory ({}). Please remove it first",
|
||||
config_path.to_str().unwrap(),
|
||||
data_dir.to_str().unwrap(),
|
||||
);
|
||||
return Err(ConfigError::SerializationError(msg));
|
||||
} else {
|
||||
let config = GlobalWalletConfig::new(config_path.to_str().unwrap())?;
|
||||
(wallet_path, config)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Check API secrets, if ok, return config
|
||||
check_api_secret_file(chain_type, Some(path.clone()), OWNER_API_SECRET_FILE_NAME)?;
|
||||
check_api_secret_file(chain_type, Some(path), API_SECRET_FILE_NAME)?;
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
impl Default for GlobalWalletConfigMembers {
|
||||
fn default() -> GlobalWalletConfigMembers {
|
||||
GlobalWalletConfigMembers {
|
||||
config_file_version: Some(2),
|
||||
logging: Some(LoggingConfig::default()),
|
||||
tor: Some(TorConfig::default()),
|
||||
wallet: WalletConfig::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GlobalWalletConfig {
|
||||
fn default() -> GlobalWalletConfig {
|
||||
GlobalWalletConfig {
|
||||
config_file_path: None,
|
||||
members: Some(GlobalWalletConfigMembers::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalWalletConfig {
|
||||
/// Same as GlobalConfig::default() but further tweaks parameters to
|
||||
/// apply defaults for each chain type
|
||||
pub fn for_chain(chain_type: &global::ChainTypes) -> GlobalWalletConfig {
|
||||
let mut defaults_conf = GlobalWalletConfig::default();
|
||||
let defaults = &mut defaults_conf.members.as_mut().unwrap().wallet;
|
||||
defaults.chain_type = Some(*chain_type);
|
||||
|
||||
match *chain_type {
|
||||
global::ChainTypes::Mainnet => {}
|
||||
global::ChainTypes::Testnet => {
|
||||
defaults.api_listen_port = 13415;
|
||||
defaults.check_node_api_http_addr = "http://127.0.0.1:13413".to_owned();
|
||||
}
|
||||
global::ChainTypes::UserTesting => {
|
||||
defaults.api_listen_port = 23415;
|
||||
defaults.check_node_api_http_addr = "http://127.0.0.1:23413".to_owned();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
defaults_conf
|
||||
}
|
||||
/// Requires the path to a config file
|
||||
pub fn new(file_path: &str) -> Result<GlobalWalletConfig, ConfigError> {
|
||||
let mut return_value = GlobalWalletConfig::default();
|
||||
return_value.config_file_path = Some(PathBuf::from(&file_path));
|
||||
|
||||
// Config file path is given but not valid
|
||||
let config_file = return_value.config_file_path.clone().unwrap();
|
||||
if !config_file.exists() {
|
||||
return Err(ConfigError::FileNotFoundError(String::from(
|
||||
config_file.to_str().unwrap(),
|
||||
)));
|
||||
}
|
||||
|
||||
// Try to parse the config file if it exists, explode if it does exist but
|
||||
// something's wrong with it
|
||||
return_value.read_config()
|
||||
}
|
||||
|
||||
/// Read config
|
||||
fn read_config(mut self) -> Result<GlobalWalletConfig, ConfigError> {
|
||||
let config_file_path = self.config_file_path.as_mut().unwrap();
|
||||
let contents = fs::read_to_string(config_file_path.clone())?;
|
||||
let migrated = GlobalWalletConfig::migrate_config_file_version_none_to_2(
|
||||
contents,
|
||||
config_file_path.to_owned(),
|
||||
)?;
|
||||
let fixed = GlobalWalletConfig::fix_warning_level(migrated);
|
||||
let decoded: Result<GlobalWalletConfigMembers, toml::de::Error> = toml::from_str(&fixed);
|
||||
match decoded {
|
||||
Ok(gc) => {
|
||||
self.members = Some(gc);
|
||||
Ok(self)
|
||||
}
|
||||
Err(e) => Err(ConfigError::ParseError(
|
||||
String::from(self.config_file_path.as_mut().unwrap().to_str().unwrap()),
|
||||
format!("{}", e),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Update paths
|
||||
pub fn update_paths(&mut self, wallet_home: &PathBuf, node_home: &Path) {
|
||||
let mut data_file_dir = wallet_home.to_path_buf();
|
||||
let mut node_secret_path = node_home.to_path_buf();
|
||||
let mut secret_path = wallet_home.to_path_buf();
|
||||
let mut log_path = wallet_home.to_path_buf();
|
||||
let tor_path = wallet_home.to_path_buf();
|
||||
node_secret_path.push(API_SECRET_FILE_NAME);
|
||||
data_file_dir.push(GRIN_WALLET_DIR);
|
||||
secret_path.push(OWNER_API_SECRET_FILE_NAME);
|
||||
log_path.push(WALLET_LOG_FILE_NAME);
|
||||
self.members.as_mut().unwrap().wallet.data_file_dir =
|
||||
data_file_dir.to_str().unwrap().to_owned();
|
||||
self.members.as_mut().unwrap().wallet.node_api_secret_path =
|
||||
Some(node_secret_path.to_str().unwrap().to_owned());
|
||||
self.members.as_mut().unwrap().wallet.api_secret_path =
|
||||
Some(secret_path.to_str().unwrap().to_owned());
|
||||
self.members
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.logging
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.log_file_path = log_path.to_str().unwrap().to_owned();
|
||||
self.members
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.tor
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.send_config_dir = tor_path.to_str().unwrap().to_owned();
|
||||
}
|
||||
|
||||
/// Serialize config
|
||||
pub fn ser_config(&mut self) -> Result<String, ConfigError> {
|
||||
let encoded: Result<String, toml::ser::Error> =
|
||||
toml::to_string(self.members.as_mut().unwrap());
|
||||
match encoded {
|
||||
Ok(enc) => Ok(enc),
|
||||
Err(e) => Err(ConfigError::SerializationError(format!("{}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Write configuration to a file
|
||||
pub fn write_to_file(
|
||||
&mut self,
|
||||
name: &str,
|
||||
migration: bool,
|
||||
old_config: Option<String>,
|
||||
old_version: Option<u32>,
|
||||
) -> Result<(), ConfigError> {
|
||||
let conf_out = self.ser_config()?;
|
||||
let commented_config = if migration {
|
||||
migrate_comments(old_config.unwrap(), conf_out, old_version)
|
||||
} else {
|
||||
let fixed_config = GlobalWalletConfig::fix_log_level(conf_out);
|
||||
insert_comments(fixed_config)
|
||||
};
|
||||
let mut file = File::create(name)?;
|
||||
file.write_all(commented_config.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
/// This migration does the following:
|
||||
/// - Adds "config_file_version = 2"
|
||||
/// - Introduce new key config_file_version, [tor.bridge] and [tor.proxy]
|
||||
/// - Migrate old config key/value and comments while it does not conflict with newly indroduced key and comments
|
||||
fn migrate_config_file_version_none_to_2(
|
||||
config_str: String,
|
||||
config_file_path: PathBuf,
|
||||
) -> Result<String, ConfigError> {
|
||||
let config: GlobalWalletConfigMembers =
|
||||
toml::from_str(&GlobalWalletConfig::fix_warning_level(config_str.clone())).unwrap();
|
||||
if config.config_file_version.is_some() {
|
||||
return Ok(config_str);
|
||||
}
|
||||
let adjusted_config = GlobalWalletConfigMembers {
|
||||
config_file_version: GlobalWalletConfigMembers::default().config_file_version,
|
||||
tor: Some(TorConfig {
|
||||
bridge: TorBridgeConfig::default(),
|
||||
proxy: TorProxyConfig::default(),
|
||||
..config.tor.unwrap_or_default()
|
||||
}),
|
||||
..config
|
||||
};
|
||||
let mut gc = GlobalWalletConfig {
|
||||
members: Some(adjusted_config),
|
||||
config_file_path: Some(config_file_path.clone()),
|
||||
};
|
||||
let str_path = config_file_path.into_os_string().into_string().unwrap();
|
||||
gc.write_to_file(
|
||||
&str_path,
|
||||
true,
|
||||
Some(config_str),
|
||||
config.config_file_version,
|
||||
)?;
|
||||
let adjusted_config_str = fs::read_to_string(str_path.clone())?;
|
||||
Ok(adjusted_config_str)
|
||||
}
|
||||
|
||||
// For forwards compatibility old config needs `Warning` log level changed to standard log::Level `WARN`
|
||||
fn fix_warning_level(conf: String) -> String {
|
||||
conf.replace("Warning", "WARN")
|
||||
}
|
||||
|
||||
// For backwards compatibility only first letter of log level should be capitalised.
|
||||
fn fix_log_level(conf: String) -> String {
|
||||
conf.replace("TRACE", "Trace")
|
||||
.replace("DEBUG", "Debug")
|
||||
.replace("INFO", "Info")
|
||||
.replace("WARN", "Warning")
|
||||
.replace("ERROR", "Error")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2021 The Grin Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Crate wrapping up the Grin binary and configuration file
|
||||
|
||||
#![deny(non_upper_case_globals)]
|
||||
#![deny(non_camel_case_types)]
|
||||
#![deny(non_snake_case)]
|
||||
#![deny(unused_mut)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
use grin_core as core;
|
||||
use grin_util as util;
|
||||
|
||||
mod comments;
|
||||
pub mod config;
|
||||
pub mod types;
|
||||
|
||||
pub use crate::config::{
|
||||
config_file_exists, initial_setup_wallet, GRIN_WALLET_DIR, WALLET_CONFIG_FILE_NAME,
|
||||
};
|
||||
pub use crate::types::{
|
||||
ConfigError, GlobalWalletConfig, GlobalWalletConfigMembers, TorConfig, WalletConfig,
|
||||
};
|
||||
@@ -0,0 +1,273 @@
|
||||
// Copyright 2021 The Grin Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Public types for config modules
|
||||
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::core::global::ChainTypes;
|
||||
use crate::util::logger::LoggingConfig;
|
||||
|
||||
/// Command-line wallet configuration
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct WalletConfig {
|
||||
/// Chain parameters (default to Mainnet if none at the moment)
|
||||
pub chain_type: Option<ChainTypes>,
|
||||
/// The port this wallet will run on
|
||||
pub api_listen_port: u16,
|
||||
/// The port this wallet's owner API will run on
|
||||
pub owner_api_listen_port: Option<u16>,
|
||||
/// Location of the secret for basic auth on the Owner API
|
||||
pub api_secret_path: Option<String>,
|
||||
/// Location of the node api secret for basic auth on the Grin API
|
||||
pub node_api_secret_path: Option<String>,
|
||||
/// The api address of a running server node against which transaction inputs
|
||||
/// will be checked during send
|
||||
pub check_node_api_http_addr: String,
|
||||
/// Whether to include foreign API endpoints on the Owner API
|
||||
pub owner_api_include_foreign: Option<bool>,
|
||||
/// The directory in which wallet files are stored
|
||||
pub data_file_dir: String,
|
||||
/// If Some(true), don't cache commits alongside output data
|
||||
/// speed improvement, but your commits are in the database
|
||||
pub no_commit_cache: Option<bool>,
|
||||
/// TLS certificate file
|
||||
pub tls_certificate_file: Option<String>,
|
||||
/// TLS certificate private key file
|
||||
pub tls_certificate_key: Option<String>,
|
||||
/// Whether to use the black background color scheme for command line
|
||||
/// if enabled, wallet command output color will be suitable for black background terminal
|
||||
pub dark_background_color_scheme: Option<bool>,
|
||||
/// Scaling factor from transaction weight to transaction fee
|
||||
/// should match accept_fee_base parameter in grin-server
|
||||
pub accept_fee_base: Option<u64>,
|
||||
}
|
||||
|
||||
impl Default for WalletConfig {
|
||||
fn default() -> WalletConfig {
|
||||
WalletConfig {
|
||||
chain_type: Some(ChainTypes::Mainnet),
|
||||
api_listen_port: 3415,
|
||||
owner_api_listen_port: Some(WalletConfig::default_owner_api_listen_port()),
|
||||
api_secret_path: Some(".owner_api_secret".to_string()),
|
||||
node_api_secret_path: Some(".foreign_api_secret".to_string()),
|
||||
check_node_api_http_addr: "http://127.0.0.1:3413".to_string(),
|
||||
owner_api_include_foreign: Some(false),
|
||||
data_file_dir: ".".to_string(),
|
||||
no_commit_cache: Some(false),
|
||||
tls_certificate_file: None,
|
||||
tls_certificate_key: None,
|
||||
dark_background_color_scheme: Some(true),
|
||||
accept_fee_base: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WalletConfig {
|
||||
/// API Listen address
|
||||
pub fn api_listen_addr(&self) -> String {
|
||||
format!("127.0.0.1:{}", self.api_listen_port)
|
||||
}
|
||||
|
||||
/// Default listener port
|
||||
pub fn default_owner_api_listen_port() -> u16 {
|
||||
3420
|
||||
}
|
||||
|
||||
/// Default listener port
|
||||
pub fn default_accept_fee_base() -> u64 {
|
||||
500_000
|
||||
}
|
||||
|
||||
/// Use value from config file, defaulting to sensible value if missing.
|
||||
pub fn owner_api_listen_port(&self) -> u16 {
|
||||
self.owner_api_listen_port
|
||||
.unwrap_or_else(WalletConfig::default_owner_api_listen_port)
|
||||
}
|
||||
|
||||
/// Owner API listen address
|
||||
pub fn owner_api_listen_addr(&self) -> String {
|
||||
format!("127.0.0.1:{}", self.owner_api_listen_port())
|
||||
}
|
||||
|
||||
/// Accept fee base
|
||||
pub fn accept_fee_base(&self) -> u64 {
|
||||
self.accept_fee_base
|
||||
.unwrap_or_else(|| WalletConfig::default_accept_fee_base())
|
||||
}
|
||||
}
|
||||
/// Error type wrapping config errors.
|
||||
#[derive(Debug)]
|
||||
pub enum ConfigError {
|
||||
/// Error with parsing of config file
|
||||
ParseError(String, String),
|
||||
|
||||
/// Error with fileIO while reading config file
|
||||
FileIOError(String, String),
|
||||
|
||||
/// No file found
|
||||
FileNotFoundError(String),
|
||||
|
||||
/// Error serializing config values
|
||||
SerializationError(String),
|
||||
|
||||
/// Path doesn't exist
|
||||
PathNotFoundError(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for ConfigError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
ConfigError::ParseError(ref file_name, ref message) => write!(
|
||||
f,
|
||||
"Error parsing configuration file at {} - {}",
|
||||
file_name, message
|
||||
),
|
||||
ConfigError::FileIOError(ref file_name, ref message) => {
|
||||
write!(f, "{} {}", message, file_name)
|
||||
}
|
||||
ConfigError::FileNotFoundError(ref file_name) => {
|
||||
write!(f, "Configuration file not found: {}", file_name)
|
||||
}
|
||||
ConfigError::SerializationError(ref message) => {
|
||||
write!(f, "Error serializing configuration: {}", message)
|
||||
}
|
||||
ConfigError::PathNotFoundError(ref message) => write!(f, "Path not found: {}", message),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for ConfigError {
|
||||
fn from(error: io::Error) -> ConfigError {
|
||||
ConfigError::FileIOError(
|
||||
String::from(""),
|
||||
format!("Error loading config file: {}", error),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Tor configuration
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TorConfig {
|
||||
/// whether to skip any attempts to send via TOR
|
||||
pub skip_send_attempt: Option<bool>,
|
||||
/// Whether to start tor listener on listener startup (default true)
|
||||
pub use_tor_listener: bool,
|
||||
/// Just the address of the socks proxy for now
|
||||
pub socks_proxy_addr: String,
|
||||
/// Send configuration directory
|
||||
pub send_config_dir: String,
|
||||
/// tor bridge config
|
||||
#[serde(default)]
|
||||
pub bridge: TorBridgeConfig,
|
||||
/// tor proxy config
|
||||
#[serde(default)]
|
||||
pub proxy: TorProxyConfig,
|
||||
}
|
||||
|
||||
impl Default for TorConfig {
|
||||
fn default() -> TorConfig {
|
||||
TorConfig {
|
||||
skip_send_attempt: Some(false),
|
||||
use_tor_listener: true,
|
||||
socks_proxy_addr: "127.0.0.1:59050".to_owned(),
|
||||
send_config_dir: ".".into(),
|
||||
bridge: TorBridgeConfig::default(),
|
||||
proxy: TorProxyConfig::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tor Bridge Config
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TorBridgeConfig {
|
||||
/// Bridge Line
|
||||
pub bridge_line: Option<String>,
|
||||
/// Client Option
|
||||
pub client_option: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for TorBridgeConfig {
|
||||
fn default() -> TorBridgeConfig {
|
||||
TorBridgeConfig {
|
||||
bridge_line: None,
|
||||
client_option: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for TorBridgeConfig {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Tor Proxy configuration (useful for protocols such as shadowsocks)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TorProxyConfig {
|
||||
/// socks4 |socks5 | http(s)
|
||||
pub transport: Option<String>,
|
||||
/// ip or dns
|
||||
pub address: Option<String>,
|
||||
/// user for auth - socks5|https(s)
|
||||
pub username: Option<String>,
|
||||
/// pass for auth - socks5|https(s)
|
||||
pub password: Option<String>,
|
||||
/// allowed port - proxy
|
||||
pub allowed_port: Option<Vec<u16>>,
|
||||
}
|
||||
|
||||
impl Default for TorProxyConfig {
|
||||
fn default() -> TorProxyConfig {
|
||||
TorProxyConfig {
|
||||
transport: None,
|
||||
address: None,
|
||||
username: None,
|
||||
password: None,
|
||||
allowed_port: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for TorProxyConfig {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Wallet should be split into a separate configuration file
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct GlobalWalletConfig {
|
||||
/// Keep track of the file we've read
|
||||
pub config_file_path: Option<PathBuf>,
|
||||
/// Wallet members
|
||||
pub members: Option<GlobalWalletConfigMembers>,
|
||||
}
|
||||
|
||||
/// Wallet internal members
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct GlobalWalletConfigMembers {
|
||||
/// Config file version (None == version 1)
|
||||
#[serde(default)]
|
||||
pub config_file_version: Option<u32>,
|
||||
/// Wallet configuration
|
||||
#[serde(default)]
|
||||
pub wallet: WalletConfig,
|
||||
/// Tor config
|
||||
pub tor: Option<TorConfig>,
|
||||
/// Logging config
|
||||
pub logging: Option<LoggingConfig>,
|
||||
}
|
||||
Reference in New Issue
Block a user