803 lines
15 KiB
JavaScript
803 lines
15 KiB
JavaScript
/*!
|
|
* config.js - bcoin configuration
|
|
* Copyright (c) 2016, Christopher Jeffrey (MIT License).
|
|
* https://github.com/bcoin-org/bcoin
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
var Network = require('../protocol/network');
|
|
var util = require('../utils/util');
|
|
var assert = require('assert');
|
|
var fs = require('fs');
|
|
var global = util.global;
|
|
|
|
/**
|
|
* @exports config
|
|
*/
|
|
|
|
function config(options) {
|
|
return config.parse(options);
|
|
}
|
|
|
|
/**
|
|
* Option name aliases.
|
|
* @const {Object}
|
|
*/
|
|
|
|
config.alias = {
|
|
conf: {},
|
|
env: {
|
|
'seed': 'seeds',
|
|
'node': 'nodes'
|
|
},
|
|
arg: {
|
|
'seed': 'seeds',
|
|
'node': 'nodes',
|
|
'n': 'network'
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Parse options and potentially env, args, and config.
|
|
* @param {Object} options
|
|
* @returns {Object}
|
|
*/
|
|
|
|
config.parse = function parse(options) {
|
|
var data = Object.create(null);
|
|
var raw = Object.create(null);
|
|
var arg, conf, prefix, filename, dirname;
|
|
|
|
if (!options)
|
|
options = {};
|
|
|
|
merge(data, options);
|
|
|
|
if (options.env) {
|
|
arg = config.parseEnv();
|
|
merge(raw, arg.data);
|
|
merge(data, arg);
|
|
}
|
|
|
|
if (options.arg) {
|
|
arg = config.parseArg();
|
|
merge(raw, arg.data);
|
|
merge(data, arg);
|
|
}
|
|
|
|
if (options.query) {
|
|
arg = config.parseQuery();
|
|
merge(raw, arg.data);
|
|
merge(data, arg);
|
|
}
|
|
|
|
if (options.hash) {
|
|
arg = config.parseHash();
|
|
merge(raw, arg.data);
|
|
merge(data, arg);
|
|
}
|
|
|
|
if (data.config && !util.isBrowser) {
|
|
prefix = config.getPrefix(data);
|
|
filename = data.config;
|
|
|
|
if (typeof filename !== 'string')
|
|
filename = resolve(prefix, 'bcoin.conf');
|
|
|
|
dirname = util.normalize(filename, true);
|
|
conf = config.readConfig(filename, prefix, dirname);
|
|
raw = merge(conf.data, raw);
|
|
data = merge(conf, data);
|
|
|
|
prefix = config.getPrefix(data);
|
|
|
|
if (!data.knownPeers) {
|
|
filename = resolve(prefix, 'known-peers');
|
|
data.knownPeers = config.readKnown(filename);
|
|
}
|
|
|
|
if (!data.authPeers) {
|
|
filename = resolve(prefix, 'authorized-peers');
|
|
data.authPeers = config.readAuth(filename);
|
|
}
|
|
}
|
|
|
|
data.data = raw;
|
|
|
|
// Force fast properties
|
|
// after all those merges.
|
|
util.fastProp(data);
|
|
|
|
return data;
|
|
};
|
|
|
|
/**
|
|
* Grab prefix from env, args, and options.
|
|
* @param {Object} env
|
|
* @param {Object} arg
|
|
* @param {Object} options
|
|
*/
|
|
|
|
config.getPrefix = function getPrefix(data) {
|
|
var prefix = data.prefix;
|
|
var network;
|
|
|
|
if (!prefix)
|
|
prefix = util.HOME + '/.bcoin';
|
|
|
|
network = Network.get(data.network).type;
|
|
|
|
prefix = util.normalize(prefix);
|
|
|
|
if (network !== 'main')
|
|
prefix += '/' + network;
|
|
|
|
return prefix;
|
|
};
|
|
|
|
/**
|
|
* Enforce types on parsed data.
|
|
* @param {Object} data
|
|
*/
|
|
|
|
config.parseData = function parseData(data, prefix, dirname) {
|
|
var options = {};
|
|
|
|
// Config
|
|
options.config = path(data.config);
|
|
|
|
// Options
|
|
options.network = str(data.network);
|
|
options.useWorkers = bool(data.useworkers);
|
|
options.maxWorkers = num(data.maxworkers);
|
|
options.workerTimeout = num(data.workertimeout);
|
|
options.sigcacheSize = num(data.sigcachesize);
|
|
|
|
// Node
|
|
options.prefix = path(data.prefix, null, dirname);
|
|
options.db = str(data.db);
|
|
options.maxFiles = num(data.maxfiles);
|
|
options.cacheSize = mul(data.cachesize, 1024 * 1024);
|
|
options.fast = bool(data.fast);
|
|
|
|
// Update the prefix if we're using one.
|
|
if (prefix && options.prefix)
|
|
prefix = config.getPrefix(options);
|
|
|
|
// Logger
|
|
options.logLevel = str(data.loglevel);
|
|
options.logConsole = bool(data.logconsole);
|
|
options.logFile = boolpath(data.logfile, prefix, dirname);
|
|
|
|
// Chain
|
|
options.witness = bool(data.witness);
|
|
options.forceWitness = bool(data.forcewitness);
|
|
options.prune = bool(data.prune);
|
|
options.useCheckpoints = bool(data.usecheckpoints);
|
|
options.coinCache = mul(data.coincache, 1024 * 1024);
|
|
options.indexTX = bool(data.indextx);
|
|
options.indexAddress = bool(data.indexaddress);
|
|
|
|
// Mempool
|
|
options.limitFree = bool(data.limitfree);
|
|
options.limitFreeRelay = bool(data.limitfreerelay);
|
|
options.requireStandard = bool(data.requirestandard);
|
|
options.rejectAbsurdFees = bool(data.rejectabsurdfees);
|
|
options.replaceByFee = bool(data.replacebyfee);
|
|
|
|
// Pool
|
|
options.selfish = bool(data.selfish);
|
|
options.headers = bool(data.headers);
|
|
options.compact = bool(data.compact);
|
|
options.bip151 = bool(data.bip151);
|
|
options.bip150 = bool(data.bip150);
|
|
options.identityKey = key(data.identitykey);
|
|
options.proxyServer = str(data.proxyserver);
|
|
options.seeds = list(data.seeds);
|
|
options.nodes = list(data.nodes);
|
|
options.maxOutbound = num(data.maxoutbound);
|
|
options.maxInbound = num(data.maxinbound);
|
|
options.noDiscovery = bool(data.nodiscovery);
|
|
options.port = num(data.port);
|
|
options.listen = bool(data.listen);
|
|
options.knownPeers = file(data.knownpeers, prefix, dirname, 'utf8');
|
|
options.authPeers = file(data.authpeers, prefix, dirname, 'utf8');
|
|
|
|
// Miner
|
|
options.payoutAddress = str(data.payoutaddress);
|
|
options.coinbaseFlags = str(data.coinbaseflags);
|
|
|
|
// HTTP
|
|
options.sslCert = file(data.sslcert, prefix, dirname);
|
|
options.sslKey = file(data.sslkey, prefix, dirname);
|
|
options.httpPort = num(data.httpport);
|
|
options.httpHost = str(data.httphost);
|
|
options.apiKey = str(data.apikey);
|
|
options.serviceKey = str(data.servicekey);
|
|
options.walletAuth = bool(data.walletauth);
|
|
options.noAuth = bool(data.noauth);
|
|
|
|
// Wallet
|
|
options.startHeight = num(data.startheight);
|
|
options.wipeNoReally = bool(data.wipenoreally);
|
|
|
|
options.data = data;
|
|
|
|
if (options.knownPeers != null)
|
|
options.knownPeers = config.parseKnown(options.knownPeers);
|
|
|
|
if (options.authPeers != null)
|
|
options.authPeers = config.parseAuth(options.authPeers);
|
|
|
|
// Alias
|
|
if (options.fast) {
|
|
options.headers = true;
|
|
options.useCheckpoints = true;
|
|
options.cacheSize = 300 << 20;
|
|
options.coinCache = 100 << 20;
|
|
}
|
|
|
|
return options;
|
|
};
|
|
|
|
/**
|
|
* Parse config file.
|
|
* @param {String} file
|
|
* @returns {Object}
|
|
*/
|
|
|
|
config.readConfig = function readConfig(file, prefix, dirname) {
|
|
return config.parseConfig(readFile(file), prefix, dirname);
|
|
};
|
|
|
|
/**
|
|
* Parse known peers file.
|
|
* @param {String} file
|
|
* @returns {Object}
|
|
*/
|
|
|
|
config.readKnown = function readKnown(file) {
|
|
return config.parseKnown(readFile(file));
|
|
};
|
|
|
|
/**
|
|
* Parse authorized peers file.
|
|
* @param {String} file
|
|
* @returns {Object}
|
|
*/
|
|
|
|
config.readAuth = function readAuth(file) {
|
|
return config.parseAuth(readFile(file));
|
|
};
|
|
|
|
/**
|
|
* Parse config text.
|
|
* @param {String} text
|
|
* @returns {Object}
|
|
*/
|
|
|
|
config.parseConfig = function parseConfig(text, prefix, dirname) {
|
|
var data = Object.create(null);
|
|
var i, parts, line, key, value, eq, col, alias;
|
|
|
|
assert(typeof text === 'string', 'Config must be text.');
|
|
|
|
text = text.trim();
|
|
parts = text.split(/\n+/);
|
|
|
|
for (i = 0; i < parts.length; i++) {
|
|
line = parts[i].trim();
|
|
|
|
if (line.length === 0)
|
|
continue;
|
|
|
|
if (/^\s*#/.test(line))
|
|
continue;
|
|
|
|
eq = line.indexOf('=');
|
|
col = line.indexOf(':');
|
|
|
|
if (col !== -1 && (col < eq || eq === -1))
|
|
eq = col;
|
|
|
|
if (eq === -1) {
|
|
key = line.trim();
|
|
value = '';
|
|
} else {
|
|
key = line.substring(0, eq).trim();
|
|
value = line.substring(eq + 1).trim();
|
|
}
|
|
|
|
key = key.replace(/\-/g, '').toLowerCase();
|
|
|
|
alias = config.alias.conf[key];
|
|
if (alias)
|
|
key = alias;
|
|
|
|
if (key.length === 0)
|
|
continue;
|
|
|
|
if (value.length === 0)
|
|
continue;
|
|
|
|
data[key] = value;
|
|
}
|
|
|
|
return config.parseData(data, prefix, dirname);
|
|
};
|
|
|
|
/**
|
|
* Parse arguments.
|
|
* @param {Array?} argv
|
|
* @returns {Object}
|
|
*/
|
|
|
|
config.parseArg = function parseArg(argv) {
|
|
var data = Object.create(null);
|
|
var args = [];
|
|
var i, arg, key, value, alias, equals;
|
|
|
|
if (!argv)
|
|
argv = process.argv;
|
|
|
|
argv = argv.slice(2);
|
|
|
|
while (argv.length) {
|
|
arg = argv.shift();
|
|
|
|
if (arg.indexOf('--') === 0) {
|
|
// e.g. --opt
|
|
arg = arg.split('=');
|
|
key = arg[0];
|
|
|
|
if (arg.length > 1) {
|
|
// e.g. --opt=val
|
|
value = arg.slice(1).join('=').trim();
|
|
equals = true;
|
|
} else {
|
|
value = 'true';
|
|
equals = false;
|
|
}
|
|
|
|
key = key.replace(/\-/g, '');
|
|
|
|
if (key.length === 0)
|
|
continue;
|
|
|
|
if (value.length === 0)
|
|
continue;
|
|
|
|
alias = config.alias.arg[key];
|
|
if (alias)
|
|
key = alias;
|
|
|
|
data[key] = value;
|
|
|
|
continue;
|
|
}
|
|
|
|
if (arg[0] === '-') {
|
|
// e.g. -abc
|
|
arg = arg.substring(1);
|
|
|
|
for (i = 0; i < arg.length; i++) {
|
|
key = arg[i];
|
|
alias = config.alias.arg[key];
|
|
if (alias)
|
|
key = alias;
|
|
data[key] = 'true';
|
|
equals = false;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
// e.g. foo
|
|
value = arg.trim();
|
|
|
|
if (value.length === 0)
|
|
continue;
|
|
|
|
if (key && !equals) {
|
|
data[key] = value;
|
|
key = null;
|
|
} else {
|
|
args.push(value);
|
|
}
|
|
}
|
|
|
|
data.args = args;
|
|
|
|
return config.parseData(data);
|
|
};
|
|
|
|
/**
|
|
* Parse environment variables.
|
|
* @param {Object} env
|
|
* @returns {Object}
|
|
*/
|
|
|
|
config.parseEnv = function parseEnv(env, prefix) {
|
|
var data = Object.create(null);
|
|
var i, keys, key, value, alias;
|
|
|
|
if (!env)
|
|
env = process.env;
|
|
|
|
if (!prefix)
|
|
prefix = 'BCOIN_';
|
|
|
|
keys = Object.keys(env);
|
|
|
|
for (i = 0; i < keys.length; i++) {
|
|
key = keys[i];
|
|
|
|
if (key.indexOf(prefix) !== 0)
|
|
continue;
|
|
|
|
value = env[key].trim();
|
|
|
|
key = key.substring(prefix.length);
|
|
key = key.replace(/_/g, '').toLowerCase();
|
|
|
|
if (key.length === 0)
|
|
continue;
|
|
|
|
if (value.length === 0)
|
|
continue;
|
|
|
|
alias = config.alias.env[key];
|
|
if (alias)
|
|
key = alias;
|
|
|
|
data[key] = value;
|
|
}
|
|
|
|
return config.parseData(data);
|
|
};
|
|
|
|
/**
|
|
* Parse uri querystring variables.
|
|
* @param {String} query
|
|
* @returns {Object}
|
|
*/
|
|
|
|
config.parseQuery = function parseQuery(query) {
|
|
if (query == null) {
|
|
if (!util.isBrowser || !global.location)
|
|
return {};
|
|
|
|
query = global.location.search || '';
|
|
}
|
|
|
|
return config.parseForm(query);
|
|
};
|
|
|
|
/**
|
|
* Parse uri hash variables.
|
|
* @param {String} hash
|
|
* @returns {Object}
|
|
*/
|
|
|
|
config.parseHash = function parseHash(hash) {
|
|
if (hash == null) {
|
|
if (!util.isBrowser || !global.location)
|
|
return {};
|
|
|
|
hash = global.location.hash || '';
|
|
}
|
|
|
|
return config.parseForm(hash);
|
|
};
|
|
|
|
/**
|
|
* Parse form-urlencoded variables.
|
|
* @param {String} query
|
|
* @returns {Object}
|
|
*/
|
|
|
|
config.parseForm = function parseForm(query) {
|
|
var data = Object.create(null);
|
|
var i, parts, index, pair, key, value, alias;
|
|
|
|
assert(typeof query === 'string');
|
|
|
|
if (query.length === 0)
|
|
return data;
|
|
|
|
if (query[0] === '?' || query[0] === '#')
|
|
query = query.substring(1);
|
|
|
|
parts = query.split('&');
|
|
|
|
for (i = 0; i < parts.length; i++) {
|
|
pair = parts[i];
|
|
index = pair.indexOf('=');
|
|
|
|
if (index === -1) {
|
|
key = pair;
|
|
value = '';
|
|
} else {
|
|
key = pair.substring(0, index);
|
|
value = pair.substring(index + 1);
|
|
}
|
|
|
|
key = unescape(key);
|
|
key = key.replace(/\-/g, '').toLowerCase();
|
|
|
|
if (key.length === 0)
|
|
continue;
|
|
|
|
value = unescape(value);
|
|
|
|
if (value.length === 0)
|
|
continue;
|
|
|
|
alias = config.alias.env[key];
|
|
if (alias)
|
|
key = alias;
|
|
|
|
data[key] = value;
|
|
}
|
|
|
|
return config.parseData(data);
|
|
};
|
|
|
|
/**
|
|
* Parse known peers.
|
|
* @param {String} text
|
|
* @returns {Object}
|
|
*/
|
|
|
|
config.parseKnown = function parseKnown(text) {
|
|
var lines = text.split(/\n+/);
|
|
var map = Object.create(null);
|
|
var i, line, parts, hostname, host, ip, key;
|
|
|
|
for (i = 0; i < lines.length; i++) {
|
|
line = lines[i].trim();
|
|
|
|
if (line.length === 0)
|
|
continue;
|
|
|
|
if (/^\s*#/.test(line))
|
|
continue;
|
|
|
|
parts = line.split(/\s+/);
|
|
|
|
if (parts.length < 2)
|
|
continue;
|
|
|
|
hostname = parts[0].trim().split(',');
|
|
|
|
if (hostname.length >= 2) {
|
|
host = hostname[0];
|
|
ip = hostname[1];
|
|
} else {
|
|
host = null;
|
|
ip = hostname[0];
|
|
}
|
|
|
|
key = parts[1].trim();
|
|
key = new Buffer(key, 'hex');
|
|
|
|
if (key.length !== 33)
|
|
throw new Error('Invalid key: ' + parts[1]);
|
|
|
|
if (host && host.length > 0)
|
|
map[host] = key;
|
|
|
|
if (ip.length === 0)
|
|
continue;
|
|
|
|
map[ip] = key;
|
|
}
|
|
|
|
return map;
|
|
};
|
|
|
|
/**
|
|
* Parse authorized peers.
|
|
* @param {String} text
|
|
* @returns {Buffer[]} keys
|
|
*/
|
|
|
|
config.parseAuth = function parseAuth(text) {
|
|
var lines = text.split(/\n+/);
|
|
var keys = [];
|
|
var i, line, key;
|
|
|
|
for (i = 0; i < lines.length; i++) {
|
|
line = lines[i].trim();
|
|
|
|
if (line.length === 0)
|
|
continue;
|
|
|
|
if (/^\s*#/.test(line))
|
|
continue;
|
|
|
|
key = new Buffer(line, 'hex');
|
|
|
|
if (key.length !== 33)
|
|
throw new Error('Invalid key: ' + line);
|
|
|
|
keys.push(key);
|
|
}
|
|
|
|
return keys;
|
|
};
|
|
|
|
/*
|
|
* Helpers
|
|
*/
|
|
|
|
function str(value) {
|
|
if (!value)
|
|
return null;
|
|
return value;
|
|
}
|
|
|
|
function list(value) {
|
|
if (!value)
|
|
return null;
|
|
|
|
if (typeof value !== 'string')
|
|
return null;
|
|
|
|
return value.trim().split(/\s*,\s*/);
|
|
}
|
|
|
|
function key(value) {
|
|
var key;
|
|
|
|
if (!value)
|
|
return null;
|
|
|
|
if (typeof value !== 'string')
|
|
return null;
|
|
|
|
key = new Buffer(value, 'hex');
|
|
|
|
if (key.length !== 32)
|
|
throw new Error('Invalid key: ' + value);
|
|
|
|
return key;
|
|
}
|
|
|
|
function path(value, prefix, dirname) {
|
|
if (!value)
|
|
return null;
|
|
|
|
switch (value[0]) {
|
|
case '~': // home dir
|
|
value = util.HOME + value.substring(1);
|
|
break;
|
|
case '@': // prefix
|
|
if (prefix)
|
|
value = prefix + value.substring(1);
|
|
break;
|
|
default: // dirname of config, or cwd
|
|
if (dirname)
|
|
value = resolve(dirname, value);
|
|
break;
|
|
}
|
|
|
|
return util.normalize(value);
|
|
}
|
|
|
|
function bool(value) {
|
|
if (!value)
|
|
return null;
|
|
|
|
if (value === 'true' || value === '1')
|
|
return true;
|
|
|
|
if (value === 'false' || value === '0')
|
|
return false;
|
|
|
|
return null;
|
|
}
|
|
|
|
function num(value) {
|
|
if (!value)
|
|
return null;
|
|
|
|
value = +value;
|
|
|
|
if (!isFinite(value))
|
|
return null;
|
|
|
|
return value;
|
|
}
|
|
|
|
function mul(value, mult) {
|
|
value = num(value);
|
|
if (value == null)
|
|
return value;
|
|
return value * mult;
|
|
}
|
|
|
|
function boolpath(value, prefix, dirname) {
|
|
if (!value)
|
|
return null;
|
|
|
|
if (value === 'true' || value === '1')
|
|
return true;
|
|
|
|
if (value === 'false' || value === '0')
|
|
return false;
|
|
|
|
return path(value, prefix, dirname);
|
|
}
|
|
|
|
function file(value, prefix, dirname, enc) {
|
|
if (fs.unsupported)
|
|
return null;
|
|
|
|
value = path(value, prefix, dirname);
|
|
|
|
if (!value)
|
|
return null;
|
|
|
|
try {
|
|
return fs.readFileSync(value, enc);
|
|
} catch (e) {
|
|
if (e.code === 'ENOENT')
|
|
return null;
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
function resolve(a, b) {
|
|
if (b[0] === '/')
|
|
return b;
|
|
return util.normalize(a + '/' + b);
|
|
}
|
|
|
|
function readFile(file) {
|
|
if (fs.unsupported)
|
|
return '';
|
|
|
|
if (!file)
|
|
return '';
|
|
|
|
if (typeof file !== 'string')
|
|
return '';
|
|
|
|
try {
|
|
return fs.readFileSync(file, 'utf8');
|
|
} catch (e) {
|
|
if (e.code === 'ENOENT')
|
|
return '';
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
function merge(a, b) {
|
|
var keys = Object.keys(b);
|
|
var i, key;
|
|
|
|
for (i = 0; i < keys.length; i++) {
|
|
key = keys[i];
|
|
if (b[key] != null)
|
|
a[key] = b[key];
|
|
}
|
|
|
|
return a;
|
|
}
|
|
|
|
function unescape(str) {
|
|
try {
|
|
str = decodeURIComponent(str).replace(/\+/g, ' ');
|
|
} finally {
|
|
return str.replace(/\0/g, '');
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Expose
|
|
*/
|
|
|
|
module.exports = config;
|