diff --git a/bin/cli b/bin/cli index 7bc216cb..f0d2fe30 100755 --- a/bin/cli +++ b/bin/cli @@ -2,7 +2,7 @@ 'use strict'; -var config = require('../lib/node/config'); +var Config = require('../lib/node/config'); var util = require('../lib/utils/util'); var co = require('../lib/utils/co'); var Client = require('../lib/http/client'); @@ -11,10 +11,7 @@ var Amount = require('../lib/btc/amount'); var main; function CLI() { - this.config = config.parseRaw({ - config: true, - arg: true, - env: true, + this.config = new Config({ network: 'main' }); this.argv = this.config.argv; @@ -39,36 +36,24 @@ CLI.prototype.getWallets = co(function* getWallets() { }); CLI.prototype.createWallet = co(function* createWallet() { - var options = { id: this.argv[0] }; + var options = { id: this.config.str(0) }; var wallet; - if (this.config.type) - options.type = this.config.type; + options.type = this.config.str('type'); + options.master = this.config.str('master'); + options.mnemonic = this.config.str('mnemonic'); + options.m = this.config.num('m'); + options.n = this.config.num('n'); + options.witness = this.config.bool('witness'); + options.passphrase = this.config.str('passphrase'); - if (this.config.master) - options.master = this.config.master; - - if (this.config.mnemonic) - options.master = this.config.mnemonic; - - if (this.config.m) - options.m = this.config.m >>> 0; - - if (this.config.n) - options.n = this.config.n >>> 0; - - if (this.config.witness != null) - options.witness = !!this.config.witness; - - if (this.config.passphrase) - options.passphrase = this.config.passphrase; - - if (this.config.watch) { + if (this.config.has('watch')) { options.watchOnly = true; - options.accountKey = this.config.watch; + options.accountKey = this.config.str('watch'); } wallet = yield this.client.createWallet(options); + this.log(wallet); }); @@ -78,59 +63,50 @@ CLI.prototype.getMaster = co(function* getMaster() { }); CLI.prototype.getKey = co(function* getKey() { - var address = this.argv[0]; + var address = this.config.str(0); var key = yield this.wallet.getKey(address); this.log(key); }); CLI.prototype.getWIF = co(function* getWIF() { - var address = this.argv[0]; - var key = yield this.wallet.getWIF(address, this.config.passphrase); + var address = this.config.str(0); + var key = yield this.wallet.getWIF(address, this.config.str('passphrase')); this.log(key.privateKey); }); CLI.prototype.addSharedKey = co(function* addSharedKey() { - var key = this.argv[0]; - yield this.wallet.addSharedKey(this.config.account, key); + var key = this.config.str(0); + yield this.wallet.addSharedKey(this.config.str('account'), key); this.log('Added key.'); }); CLI.prototype.removeSharedKey = co(function* removeSharedKey() { - var key = this.argv[0]; - yield this.wallet.removeSharedKey(this.config.account, key); + var key = this.config.str(0); + yield this.wallet.removeSharedKey(this.config.str('account'), key); this.log('Removed key.'); }); CLI.prototype.getSharedKeys = co(function* getSharedKeys() { - var acct = this.argv[0] || this.config.account; + var acct = this.config.str([0, 'account']); var account = yield this.wallet.getAccount(acct); this.log(account.keys); }); CLI.prototype.getAccount = co(function* getAccount() { - var acct = this.argv[0] || this.config.account; + var acct = this.config.str([0, 'account']); var account = yield this.wallet.getAccount(acct); this.log(account); }); CLI.prototype.createAccount = co(function* createAccount() { - var options = { name: this.argv[0] }; + var options = { name: this.config.str(0) }; var account; - if (this.config.type) - options.type = this.config.type; - - if (this.config.m) - options.m = this.config.m >>> 0; - - if (this.config.n) - options.n = this.config.n >>> 0; - - if (this.config.witness != null) - options.witness = !!this.config.witness; - - if (this.config.watch) - options.accountKey = this.config.watch; + options.type = this.config.str('type'); + options.m = this.config.num('m'); + options.n = this.config.num('n'); + options.witness = this.config.bool('witness'); + options.accountKey = this.config.str('watch'); account = yield this.wallet.createAccount(options); @@ -138,19 +114,19 @@ CLI.prototype.createAccount = co(function* createAccount() { }); CLI.prototype.createAddress = co(function* createAddress() { - var account = this.argv[0]; + var account = this.config.str(0); var addr = yield this.wallet.createAddress(account); this.log(addr); }); CLI.prototype.createChange = co(function* createChange() { - var account = this.argv[0]; + var account = this.config.str(0); var addr = yield this.wallet.createChange(account); this.log(addr); }); CLI.prototype.createNested = co(function* createNested() { - var account = this.argv[0]; + var account = this.config.str(0); var addr = yield this.wallet.createNested(account); this.log(addr); }); @@ -166,7 +142,7 @@ CLI.prototype.getWallet = co(function* getWallet() { }); CLI.prototype.getTX = co(function* getTX() { - var hash = this.argv[0]; + var hash = this.config.str(0); var txs, tx; if (util.isBase58(hash)) { @@ -186,7 +162,7 @@ CLI.prototype.getTX = co(function* getTX() { }); CLI.prototype.getBlock = co(function* getBlock() { - var hash = this.argv[0]; + var hash = this.config.str(0); var block; if (hash.length !== 64) @@ -203,8 +179,8 @@ CLI.prototype.getBlock = co(function* getBlock() { }); CLI.prototype.getCoin = co(function* getCoin() { - var hash = this.argv[0]; - var index = this.argv[1]; + var hash = this.config.str(0); + var index = this.config.num(1); var coins, coin; if (util.isBase58(hash)) { @@ -224,17 +200,17 @@ CLI.prototype.getCoin = co(function* getCoin() { }); CLI.prototype.getWalletHistory = co(function* getWalletHistory() { - var txs = yield this.wallet.getHistory(this.config.account); + var txs = yield this.wallet.getHistory(this.config.str('account')); this.log(txs); }); CLI.prototype.getWalletPending = co(function* getWalletPending() { - var txs = yield this.wallet.getPending(this.config.account); + var txs = yield this.wallet.getPending(this.config.str('account')); this.log(txs); }); CLI.prototype.getWalletCoins = co(function* getWalletCoins() { - var coins = yield this.wallet.getCoins(this.config.account); + var coins = yield this.wallet.getCoins(this.config.str('account')); this.log(coins); }); @@ -277,7 +253,7 @@ CLI.prototype.listenWallet = co(function* listenWallet() { }); CLI.prototype.getBalance = co(function* getBalance() { - var balance = yield this.wallet.getBalance(this.config.account); + var balance = yield this.wallet.getBalance(this.config.str('account')); this.log(balance); }); @@ -290,19 +266,19 @@ CLI.prototype.sendTX = co(function* sendTX() { var output = {}; var options, tx; - if (this.config.script) { - output.script = this.config.script; - output.value = Amount.value(this.config.value || this.argv[0]); + if (this.config.has('script')) { + output.script = this.config.str('script'); + output.value = Amount.value(this.config.num(['value', 0])); } else { - output.address = this.config.address || this.argv[0]; - output.value = Amount.value(this.config.value || this.argv[1]); + output.address = this.config.str(['address', 0]); + output.value = Amount.value(this.config.num(['value', 1])); } options = { - account: this.config.account, - passphrase: this.config.passphrase, + account: this.config.str('account'), + passphrase: this.config.str('passphrase'), outputs: [output], - rate: this.config.rate + rate: this.config.str('rate') }; tx = yield this.wallet.send(options); @@ -314,17 +290,17 @@ CLI.prototype.createTX = co(function* createTX() { var output = {}; var options, tx; - if (this.config.script) { - output.script = this.config.script; - output.value = Amount.value(this.config.value || this.argv[0]); + if (this.config.has('script')) { + output.script = this.config.str('script'); + output.value = Amount.value(this.config.str(['value', 0])); } else { - output.address = this.config.address || this.argv[0]; - output.value = Amount.value(this.config.value || this.argv[1]); + output.address = this.config.str(['address', 0]); + output.value = Amount.value(this.config.num(['value', 1])); } options = { - account: this.config.account, - passphrase: this.config.passphrase, + account: this.config.str('account'), + passphrase: this.config.str('passphrase'), outputs: [output] }; @@ -334,33 +310,37 @@ CLI.prototype.createTX = co(function* createTX() { }); CLI.prototype.signTX = co(function* signTX() { - var options = { passphrase: this.config.passphrase }; - var raw = options.tx || this.argv[0]; - var tx = yield this.wallet.sign(raw, options); + var options = {}; + var raw = this.config.str(['tx', 0]); + + options.passphrase = this.config.str('passphrase'); + + tx = yield this.wallet.sign(raw, options); + this.log(tx); }); CLI.prototype.zapWallet = co(function* zapWallet() { - var age = (this.config.age >>> 0) || 72 * 60 * 60; - yield this.wallet.zap(this.config.account, age); + var age = this.config.num('age', 72 * 60 * 60); + yield this.wallet.zap(this.config.str('account'), age); this.log('Zapped!'); }); CLI.prototype.broadcast = co(function* broadcast() { - var raw = this.argv[0] || this.config.tx; + var raw = this.config.str([0, 'tx']); var tx = yield this.client.broadcast(raw); this.log('Broadcasted:'); this.log(tx); }); CLI.prototype.viewTX = co(function* viewTX() { - var raw = this.argv[0] || this.config.tx; + var raw = this.config.str([0, 'tx']); var tx = yield this.wallet.fill(raw); this.log(tx); }); CLI.prototype.getDetails = co(function* getDetails() { - var hash = this.argv[0]; + var hash = this.config.str(0); var details = yield this.wallet.getTX(hash); this.log(details); }); @@ -371,7 +351,7 @@ CLI.prototype.getWalletBlocks = co(function* getWalletBlocks() { }); CLI.prototype.getWalletBlock = co(function* getWalletBlock() { - var height = this.argv[0] | 0; + var height = this.config.num(0); var block = yield this.wallet.getBlock(height); this.log(block); }); @@ -382,7 +362,7 @@ CLI.prototype.retoken = co(function* retoken() { }); CLI.prototype.rescan = co(function* rescan() { - var height = this.argv[0]; + var height = this.config.num(0); if (!util.isUInt32(height)) height = null; @@ -393,7 +373,7 @@ CLI.prototype.rescan = co(function* rescan() { }); CLI.prototype.reset = co(function* reset() { - var hash = this.argv[0]; + var hash = this.config.str(0); if (hash.length !== 64) hash = +hash; @@ -414,7 +394,7 @@ CLI.prototype.resendWallet = co(function* resendWallet() { }); CLI.prototype.backup = co(function* backup() { - var path = this.argv[0]; + var path = this.config.str(0); yield this.client.backup(path); @@ -422,8 +402,8 @@ CLI.prototype.backup = co(function* backup() { }); CLI.prototype.importKey = co(function* importKey() { - var key = this.argv[0]; - var account = this.config.account; + var key = this.config.str(0); + var account = this.config.str('account'); if (!key) throw new Error('No key for import.'); @@ -444,8 +424,8 @@ CLI.prototype.importKey = co(function* importKey() { }); CLI.prototype.importAddress = co(function* importKey() { - var address = this.argv[0]; - var account = this.config.account; + var address = this.config.str(0); + var account = this.config.str('account'); yield this.wallet.importAddress(account, address); this.log('Imported address.'); }); @@ -456,8 +436,8 @@ CLI.prototype.lock = co(function* lock() { }); CLI.prototype.unlock = co(function* unlock() { - var passphrase = this.argv[0]; - var timeout = +this.argv[1] || null; + var passphrase = this.config.str(0); + var timeout = this.config.num(1); yield this.wallet.unlock(passphrase, timeout); this.log('Unlocked.'); }); @@ -492,11 +472,11 @@ CLI.prototype.rpc = co(function* rpc() { CLI.prototype.handleWallet = co(function* handleWallet() { this.wallet = new Wallet({ - uri: this.config.url || this.config.uri, - apiKey: this.config.apikey, - network: this.config.network, - id: this.config.id || 'primary', - token: this.config.token + uri: this.config.str(['url', 'uri']), + apiKey: this.config.str('api-key'), + network: this.config.str('network'), + id: this.config.str('id', 'primary'), + token: this.config.str('token') }); switch (this.argv.shift()) { @@ -619,9 +599,9 @@ CLI.prototype.handleWallet = co(function* handleWallet() { CLI.prototype.handleNode = co(function* handleNode() { this.client = new Client({ - uri: this.config.url || this.config.uri, - apiKey: this.config.apikey, - network: this.config.network + uri: this.config.str(['url', 'uri']), + apiKey: this.config.str('api-key'), + network: this.config.str('network') }); switch (this.argv.shift()) { diff --git a/bin/node b/bin/node index 843be1ae..6b8dcb79 100755 --- a/bin/node +++ b/bin/node @@ -4,35 +4,22 @@ process.title = 'bcoin'; -var assert = require('assert'); var bcoin = require('../'); var co = bcoin.co; -var options, node; +var node; -options = bcoin.config({ - config: true, - arg: true, - env: true, - logLevel: 'debug', +node = new bcoin.fullnode({ logFile: true, + logLevel: 'debug', db: 'leveldb', - listen: true + listen: true, + loader: require }); -bcoin.set(options); - -node = new bcoin.fullnode(options); - node.on('error', function(err) { ; }); -process.on('uncaughtException', function(err) { - node.logger.debug(err.stack); - node.logger.error(err); - process.exit(1); -}); - co.spawn(function *() { yield node.open(); yield node.connect(); diff --git a/bin/spvnode b/bin/spvnode index 793f8a17..7d7450f5 100755 --- a/bin/spvnode +++ b/bin/spvnode @@ -8,36 +8,25 @@ var assert = require('assert'); var bcoin = require('../'); var util = bcoin.util; var co = bcoin.co; -var options, node; +var node; -options = bcoin.config({ - config: true, - arg: true, - env: true, - logLevel: 'debug', +node = bcoin.spvnode({ logFile: true, - db: 'leveldb' + logLevel: 'debug', + db: 'leveldb', + listen: true, + loader: require }); -bcoin.set(options); - -node = bcoin.spvnode(options); - node.on('error', function(err) { ; }); -process.on('uncaughtException', function(err) { - node.logger.debug(err.stack); - node.logger.error(err); - process.exit(1); -}); - co.spawn(function *() { yield node.open(); yield node.connect(); - if (process.argv.indexOf('--test') !== -1) { + if (node.config.bool('test')) { node.pool.watchAddress('1VayNert3x1KzbpzMGt2qdqrAThiRovi8'); node.pool.watchOutpoint(new bcoin.outpoint()); node.on('block', function(block) { diff --git a/etc/sample.conf b/etc/sample.conf index 93c325fa..d1ebbb81 100644 --- a/etc/sample.conf +++ b/etc/sample.conf @@ -102,6 +102,7 @@ reserved-block-sigops: 400 http-host: :: # http-port: 8332 +# ssl: true # ssl-cert: @/ssl/cert.crt # ssl-key: @/ssl/priv.key service-key: bikeshed diff --git a/lib/blockchain/chain.js b/lib/blockchain/chain.js index 6b44757f..1ac7184f 100644 --- a/lib/blockchain/chain.js +++ b/lib/blockchain/chain.js @@ -2304,6 +2304,7 @@ function ChainOptions(options) { this.network = Network.primary; this.logger = Logger.global; + this.prefix = null; this.location = null; this.db = 'memory'; this.maxFiles = 64; @@ -2342,6 +2343,19 @@ ChainOptions.prototype.fromOptions = function fromOptions(options) { this.logger = options.logger; } + if (options.spv != null) { + assert(typeof options.spv === 'boolean'); + this.spv = options.spv; + } + + if (options.prefix != null) { + assert(typeof options.prefix === 'string'); + this.prefix = options.prefix; + this.location = this.spv + ? this.prefix + '/spvchain' + : this.prefix + '/chain'; + } + if (options.location != null) { assert(typeof options.location === 'string'); this.location = options.location; @@ -2367,11 +2381,6 @@ ChainOptions.prototype.fromOptions = function fromOptions(options) { this.compression = options.compression; } - if (options.spv != null) { - assert(typeof options.spv === 'boolean'); - this.spv = options.spv; - } - if (options.prune != null) { assert(typeof options.prune === 'boolean'); this.prune = options.prune; diff --git a/lib/db/ldb.js b/lib/db/ldb.js index 734312bd..38fdca96 100644 --- a/lib/db/ldb.js +++ b/lib/db/ldb.js @@ -9,7 +9,6 @@ var assert = require('assert'); var LowlevelUp = require('./lowlevelup'); -var util = require('../utils/util'); var backends = require('./backends'); /** @@ -33,9 +32,6 @@ function LDB(options) { var target = LDB.getTarget(options); var cacheSize = options.cacheSize; - if (target.backend !== 'memory') - util.mkdir(target.location, true); - if (!cacheSize) cacheSize = 16 << 20; diff --git a/lib/http/base.js b/lib/http/base.js index 4e537759..851c9b2a 100644 --- a/lib/http/base.js +++ b/lib/http/base.js @@ -546,7 +546,6 @@ HTTPBaseOptions.prototype.fromOptions = function fromOptions(options) { if (options.key != null) { assert(typeof options.key === 'string' || Buffer.isBuffer(options.key)); this.key = options.key; - this.ssl = true; } if (options.cert != null) { @@ -572,6 +571,7 @@ HTTPBaseOptions.prototype.fromOptions = function fromOptions(options) { if (options.ssl != null) { assert(typeof options.ssl === 'boolean'); assert(this.key, 'SSL specified with no provided key.'); + assert(this.cert, 'SSL specified with no provided cert.'); this.ssl = options.ssl; } diff --git a/lib/http/rpc.js b/lib/http/rpc.js index 99ea66f0..d6b29b6d 100644 --- a/lib/http/rpc.js +++ b/lib/http/rpc.js @@ -6,8 +6,8 @@ 'use strict'; -var fs = require('fs'); var EventEmitter = require('events').EventEmitter; +var fs = require('../utils/fs'); var util = require('../utils/util'); var co = require('../utils/co'); var crypto = require('../crypto/crypto'); @@ -2862,7 +2862,7 @@ RPC.prototype.dumpwallet = co(function* dumpwallet(args, help) { if (fs.unsupported) return out; - yield writeFile(file, out); + yield fs.writeFile(file, out, 'utf8'); return out; }); @@ -3296,7 +3296,7 @@ RPC.prototype.importwallet = co(function* importwallet(args, help) { if (rescan && this.chain.options.prune) throw new RPCError('Cannot rescan when pruned.'); - data = yield readFile(file, 'utf8'); + data = yield fs.readFile(file, 'utf8'); lines = data.split(/\n+/); keys = []; @@ -4301,18 +4301,6 @@ function reverseEndian(data) { } } -function writeFile(file, data) { - return new Promise(function(resolve, reject) { - fs.writeFile(file, data, co.wrap(resolve, reject)); - }); -} - -function readFile(file, enc) { - return new Promise(function(resolve, reject) { - fs.readFile(file, enc, co.wrap(resolve, reject)); - }); -} - function sortTX(txs) { return txs.sort(function(a, b) { return a.ps - b.ps; diff --git a/lib/http/server.js b/lib/http/server.js index b3c888ca..d7422099 100644 --- a/lib/http/server.js +++ b/lib/http/server.js @@ -27,6 +27,7 @@ var HD = require('../hd/hd'); var Script = require('../script/script'); var crypto = require('../crypto/crypto'); var Network = require('../protocol/network'); +var fs = require('../utils/fs'); var pkg = require('../pkg'); var cob = co.cob; var RPC; @@ -48,6 +49,7 @@ function HTTPServer(options) { EventEmitter.call(this); this.options = new HTTPOptions(options); + this.options.load(); this.network = this.options.network; this.logger = this.options.logger; @@ -1797,9 +1799,10 @@ function HTTPOptions(options) { this.sockets = true; this.host = '127.0.0.1'; this.port = this.network.rpcPort; + this.keyFile = null; + this.certFile = null; this.key = null; this.cert = null; - this.ca = null; this.contentType = 'json'; this.fromOptions(options); @@ -1869,19 +1872,26 @@ HTTPOptions.prototype.fromOptions = function fromOptions(options) { this.port = options.port; } - if (options.key != null) { - assert(typeof options.key === 'string' || Buffer.isBuffer(options.key)); - this.key = options.key; + if (options.prefix != null) { + assert(typeof options.prefix === 'string'); + this.prefix = options.prefix; + this.keyFile = this.prefix + '/key.pem'; + this.certFile = this.prefix + '/cert.pem'; } - if (options.cert != null) { - assert(typeof options.cert === 'string' || Buffer.isBuffer(options.cert)); - this.cert = options.cert; + if (options.ssl != null) { + assert(typeof options.ssl === 'boolean'); + this.ssl = options.ssl; } - if (options.ca != null) { - assert(Array.isArray(options.ca)); - this.ca = options.ca; + if (options.keyFile != null) { + assert(typeof options.keyFile === 'string'); + this.keyFile = options.keyFile; + } + + if (options.certFile != null) { + assert(typeof options.certFile === 'string'); + this.certFile = options.certFile; } // Allow no-auth implicitly @@ -1894,6 +1904,22 @@ HTTPOptions.prototype.fromOptions = function fromOptions(options) { return this; }; +/** + * Load key and cert file. + * @private + */ + +HTTPOptions.prototype.load = function load() { + if (!this.ssl) + return; + + if (this.keyFile) + this.key = fs.readFileSync(this.keyFile); + + if (this.certFile) + this.cert = fs.readFileSync(this.certFile); +}; + /** * Instantiate http options from object. * @param {Object} options diff --git a/lib/mempool/mempool.js b/lib/mempool/mempool.js index 7b30c15f..cba19eb0 100644 --- a/lib/mempool/mempool.js +++ b/lib/mempool/mempool.js @@ -1984,6 +1984,7 @@ function MempoolOptions(options) { this.expiryTime = policy.MEMPOOL_EXPIRY_TIME; this.minRelay = this.network.minRelay; + this.prefix = null; this.location = null; this.db = 'memory'; this.maxFiles = 64; @@ -2090,6 +2091,12 @@ MempoolOptions.prototype.fromOptions = function fromOptions(options) { this.minRelay = options.minRelay; } + if (options.prefix != null) { + assert(typeof options.prefix === 'string'); + this.prefix = options.prefix; + this.location = this.prefix + '/mempool'; + } + if (options.location != null) { assert(typeof options.location === 'string'); this.location = options.location; diff --git a/lib/net/bip150.js b/lib/net/bip150.js index f7d4cc35..f7aa79e3 100644 --- a/lib/net/bip150.js +++ b/lib/net/bip150.js @@ -20,6 +20,8 @@ var base58 = require('../utils/base58'); var encoding = require('../utils/encoding'); var IP = require('../utils/ip'); var dns = require('./dns'); +var fs = require('../utils/fs'); +var Logger = require('../node/logger'); /** * Represents a BIP150 input/output stream. @@ -466,8 +468,9 @@ function AuthDB(options) { if (!(this instanceof AuthDB)) return new AuthDB(options); - this.logger = null; + this.logger = Logger.global; this.resolve = dns.lookup; + this.prefix = null; this.dnsKnown = []; this.known = {}; @@ -476,26 +479,6 @@ function AuthDB(options) { this._init(options); } -/** - * Open auth database (lookup known peers). - * @method - * @returns {Promise} - */ - -AuthDB.prototype.open = co(function* open() { - yield this.lookup(); -}); - -/** - * Close auth database. - * @method - * @returns {Promise} - */ - -AuthDB.prototype.close = co(function* close() { - ; -}); - /** * Initialize authdb with options. * @param {Object} options @@ -524,8 +507,35 @@ AuthDB.prototype._init = function _init(options) { assert(Array.isArray(options.authPeers)); this.setAuthorized(options.authPeers); } + + if (options.prefix != null) { + assert(typeof options.prefix === 'string'); + this.prefix = options.prefix; + } }; +/** + * Open auth database (lookup known peers). + * @method + * @returns {Promise} + */ + +AuthDB.prototype.open = co(function* open() { + yield this.readKnown(); + yield this.readAuth(); + yield this.lookup(); +}); + +/** + * Close auth database. + * @method + * @returns {Promise} + */ + +AuthDB.prototype.close = co(function* close() { + ; +}); + /** * Add a known peer. * @param {String} host - Peer Hostname @@ -647,14 +657,12 @@ AuthDB.prototype.populate = co(function* populate(addr, key) { assert(addr.type === IP.types.DNS, 'Resolved host passed.'); - if (this.logger) - this.logger.info('Resolving authorized hosts from: %s.', addr.host); + this.logger.info('Resolving authorized hosts from: %s.', addr.host); try { hosts = yield this.resolve(addr.host); } catch (e) { - if (this.logger) - this.logger.error(e); + this.logger.error(e); return; } @@ -668,6 +676,140 @@ AuthDB.prototype.populate = co(function* populate(addr, key) { } }); +/** + * Parse known peers. + * @param {String} text + * @returns {Object} + */ + +AuthDB.prototype.readKnown = co(function* readKnown() { + var file, text; + + if (fs.unsupported) + return; + + if (!this.prefix) + return; + + file = this.prefix + '/known-peers'; + + try { + text = yield fs.readFile(file, 'utf8'); + } catch (e) { + if (e.code === 'ENOENT') + return; + throw e; + } + + this.parseKnown(text); +}); + +/** + * Parse known peers. + * @param {String} text + * @returns {Object} + */ + +AuthDB.prototype.parseKnown = function parseKnown(text) { + var lines = text.split(/\n+/); + 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) + this.addKnown(host, key); + + if (ip.length === 0) + continue; + + this.addKnown(ip, key); + } +}; + +/** + * Parse known peers. + * @param {String} text + * @returns {Object} + */ + +AuthDB.prototype.readAuth = co(function* readAuth() { + var file, text; + + if (fs.unsupported) + return; + + if (!this.prefix) + return; + + file = this.prefix + '/authorized-peers'; + + try { + text = yield fs.readFile(file, 'utf8'); + } catch (e) { + if (e.code === 'ENOENT') + return; + throw e; + } + + this.parseAuth(text); +}); + +/** + * Parse authorized peers. + * @param {String} text + * @returns {Buffer[]} keys + */ + +AuthDB.prototype.parseAuth = function parseAuth(text) { + var lines = text.split(/\n+/); + 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); + + this.addAuthorized(key); + } +}; + /* * Expose */ diff --git a/lib/net/hostlist.js b/lib/net/hostlist.js index 4c80735c..88d43d01 100644 --- a/lib/net/hostlist.js +++ b/lib/net/hostlist.js @@ -7,7 +7,6 @@ 'use strict'; var assert = require('assert'); -var fs = require('fs'); var util = require('../utils/util'); var IP = require('../utils/ip'); var co = require('../utils/co'); @@ -20,6 +19,7 @@ var common = require('./common'); var seeds = require('./seeds'); var dns = require('./dns'); var Logger = require('../node/logger'); +var fs = require('../utils/fs'); /** * Host List @@ -195,7 +195,7 @@ HostList.prototype.close = co(function* close() { */ HostList.prototype.start = function start() { - if (!this.options.hostLocation) + if (!this.options.location) return; assert(this.timer == null); @@ -207,7 +207,7 @@ HostList.prototype.start = function start() { */ HostList.prototype.stop = function stop() { - if (!this.options.hostLocation) + if (!this.options.location) return; assert(this.timer != null); @@ -249,14 +249,17 @@ HostList.prototype.injectSeeds = function injectSeeds() { */ HostList.prototype.loadFile = co(function* loadFile() { - var filename = this.options.hostLocation; + var filename = this.options.location; var data, json; + if (fs.unsupported) + return; + if (!filename) return; try { - data = yield readFile(filename, 'utf8'); + data = yield fs.readFile(filename, 'utf8'); } catch (e) { if (e.code === 'ENOENT') return; @@ -275,9 +278,12 @@ HostList.prototype.loadFile = co(function* loadFile() { */ HostList.prototype.flush = co(function* flush() { - var filename = this.options.hostLocation; + var filename = this.options.location; var json, data; + if (fs.unsupported) + return; + if (!filename) return; @@ -287,7 +293,7 @@ HostList.prototype.flush = co(function* flush() { data = JSON.stringify(json); try { - yield writeFile(filename, data, 'utf8'); + yield fs.writeFile(filename, data, 'utf8'); } catch (e) { this.logger.warning('Writing hosts failed.'); this.logger.error(e); @@ -1473,7 +1479,8 @@ function HostListOptions(options) { this.maxBuckets = 20; this.maxEntries = 50; - this.hostLocation = null; + this.prefix = null; + this.location = null; this.flushInterval = 120000; if (options) @@ -1569,9 +1576,15 @@ HostListOptions.prototype.fromOptions = function fromOptions(options) { this.maxEntries = options.maxEntries; } - if (options.hostLocation != null) { - assert(typeof options.hostLocation === 'string'); - this.hostLocation = options.hostLocation; + if (options.prefix != null) { + assert(typeof options.prefix === 'string'); + this.prefix = options.prefix; + this.location = this.prefix + '/hosts.json'; + } + + if (options.location != null) { + assert(typeof options.location === 'string'); + this.location = options.location; } if (options.flushInterval != null) { @@ -1585,33 +1598,6 @@ HostListOptions.prototype.fromOptions = function fromOptions(options) { return this; }; -/* - * Helpers - */ - -function readFile(filename, enc) { - return new Promise(function(resolve, reject) { - var err; - - if (fs.unsupported) { - err = new Error('File not found.'); - err.code = 'ENOENT'; - return reject(err); - } - - fs.readFile(filename, enc, co.wrap(resolve, reject)); - }); -} - -function writeFile(filename, data, enc) { - return new Promise(function(resolve, reject) { - if (fs.unsupported) - return resolve(); - - fs.writeFile(filename, data, enc, co.wrap(resolve, reject)); - }); -} - /* * Expose */ diff --git a/lib/net/pool.js b/lib/net/pool.js index 35cf440d..7a94578d 100644 --- a/lib/net/pool.js +++ b/lib/net/pool.js @@ -221,8 +221,6 @@ Pool.prototype._open = co(function* _open() { else yield this.chain.open(); - yield this.hosts.open(); - this.logger.info('Pool loaded (maxpeers=%d).', this.options.maxOutbound); if (this.options.bip150) { @@ -3727,6 +3725,7 @@ function PoolOptions(options) { this.nonces = new NonceList(); + this.prefix = null; this.checkpoints = true; this.spv = false; this.bip37 = false; @@ -3799,6 +3798,11 @@ PoolOptions.prototype.fromOptions = function fromOptions(options) { this.mempool = options.mempool; } + if (options.prefix != null) { + assert(typeof options.prefix === 'string'); + this.prefix = options.prefix; + } + if (options.checkpoints != null) { assert(typeof options.checkpoints === 'boolean'); assert(options.checkpoints === this.chain.options.checkpoints); diff --git a/lib/node/config.js b/lib/node/config.js index 98bcf031..eb2e23af 100644 --- a/lib/node/config.js +++ b/lib/node/config.js @@ -7,16 +7,36 @@ 'use strict'; var assert = require('assert'); -var fs = require('fs'); +var fs = require('../utils/fs'); var util = require('../utils/util'); var global = util.global; /** - * @exports node/config + * Node Config + * @alias module:node.Config + * @constructor + * @param {Object} options */ -function config(options) { - return config.parse(options); +function Config(options) { + if (!(this instanceof Config)) + return new Config(options); + + this.options = Object.create(null); + this.data = Object.create(null); + this.env = Object.create(null); + this.args = Object.create(null); + this.argv = []; + this.query = Object.create(null); + this.hash = Object.create(null); + this.prefix = null; + this.network = null; + + if (options) + this.inject(options); + + this.init(); + this.open(); } /** @@ -24,7 +44,7 @@ function config(options) { * @const {Object} */ -config.alias = { +Config.alias = { conf: {}, env: { 'seed': 'seeds', @@ -38,319 +58,512 @@ config.alias = { }; /** - * Parse env and args. - * @param {Object} options - * @returns {Object} + * Initialize the config. + * @private */ -config.parseArgs = function parseArgs(options) { - var data = Object.create(null); - var argv = []; - var raw; +Config.prototype.init = function init() { + this.parseHash(); + this.parseQuery(); + this.parseEnv(); + this.parseArg(); - if (options.network != null) { - assert(isAlpha(options.network), 'Bad network.'); - data.network = options.network; - } - - if (options.prefix != null) { - assert(typeof options.prefix === 'string'); - assert(typeof options.prefix.length > 0); - data.prefix = options.prefix; - } - - if (options.config != null) { - if (typeof options.config === 'string') { - assert(options.config.length > 0); - data.config = options.config; - } else { - assert(typeof options.config === 'boolean'); - } - } - - if (options.env) { - raw = config.parseEnv(); - merge(data, raw); - } - - if (options.arg) { - raw = config.parseArg(); - argv = raw.argv; - merge(data, raw.data); - } - - if (options.query) { - raw = config.parseQuery(); - merge(data, raw); - } - - if (options.hash) { - raw = config.parseHash(); - merge(data, raw); - } - - data.argv = argv; - - return data; + this.network = this.getNetwork(); + this.prefix = this.getPrefix(); }; /** - * Parse env, args, and config. - * @param {Object} options - * @returns {Object} + * Open the config. + * @private */ -config.parseRaw = function parseRaw(options) { - var data = config.parseArgs(options); - var prefix = config.getPrefix(data); - var file = config.getFile(prefix, data); - var argv = data.argv; - var raw; +Config.prototype.open = function open() { + var file, text; - if (options.config) { - raw = config.readConfig(file); - merge(raw, data); - data = raw; - prefix = config.getPrefix(data); + if (fs.unsupported) + return; + + file = this.getFile(); + + try { + text = fs.readFileSync(file, 'utf8'); + } catch (e) { + if (e.code === 'ENOENT') + return; + throw e; } - data.argv = argv; - data.prefix = prefix; - data.config = file; + this.parseConfig(text); - return data; + this.network = this.getNetwork(); + this.prefix = this.getPrefix(); }; /** - * Parse options and potentially env, args, and config. + * Inject options. * @param {Object} options - * @returns {Object} */ -config.parse = function parse(options) { - var data = config.parseRaw(options); - var opt = config.toOptions(data); +Config.prototype.inject = function inject(options) { var keys = Object.keys(options); - var i, key, file; + var i, key, value; for (i = 0; i < keys.length; i++) { key = keys[i]; - - switch (key) { - case 'env': - case 'arg': - case 'query': - case 'hash': - case 'config': - case 'raw': - continue; - } - - if (opt[key] != null) - continue; - - opt[key] = options[key]; + value = options[key]; + this.set(key, value); } - - if (options.config) { - if (!opt.knownPeers) { - file = resolve(data.prefix, 'known-peers'); - opt.knownPeers = config.readKnown(file); - } - - if (!opt.authPeers) { - file = resolve(data.prefix, 'authorized-peers'); - opt.authPeers = config.readAuth(file); - } - } - - return opt; }; /** - * Grab prefix from data. + * Set default option. + * @param {String} key + * @param {Object} value + */ + +Config.prototype.set = function set(key, value) { + assert(typeof key === 'string', 'Key must be a string.'); + + if (value == null) + return; + + key = key.toLowerCase().replace(/-/g, ''); + + this.options[key] = value; +}; + +/** + * Test whether a config option is present. + * @param {String} key + * @returns {Boolean} + */ + +Config.prototype.has = function has(key) { + if (typeof key === 'number') { + assert(key >= 0, 'Index must be positive.'); + if (key >= this.argv.length) + return false; + return true; + } + + assert(typeof key === 'string', 'Key must be a string.'); + + key = key.toLowerCase().replace(/-/g, ''); + + if (this.hash[key] != null) + return true; + + if (this.query[key] != null) + return true; + + if (this.args[key] != null) + return true; + + if (this.env[key] != null) + return true; + + if (this.data[key] != null) + return true; + + if (this.options[key] != null) + return true; + + return false; +}; + +/** + * Get a config option. + * @param {String} key + * @param {Object?} fallback + * @returns {Object|null} + */ + +Config.prototype.get = function get(key, fallback) { + var i, keys, value; + + if (fallback === undefined) + fallback = null; + + if (Array.isArray(key)) { + keys = key; + for (i = 0; i < keys.length; i++) { + key = keys[i]; + value = this.get(key); + if (value !== null) + return value; + } + return fallback; + } + + if (typeof key === 'number') { + assert(key >= 0, 'Index must be positive.'); + if (key >= this.argv.length) + return fallback; + return this.argv[key]; + } + + assert(typeof key === 'string', 'Key must be a string.'); + + key = key.toLowerCase().replace(/-/g, ''); + + if (this.hash[key] != null) + return this.hash[key]; + + if (this.query[key] != null) + return this.query[key]; + + if (this.args[key] != null) + return this.args[key]; + + if (this.env[key] != null) + return this.env[key]; + + if (this.data[key] != null) + return this.data[key]; + + if (this.options[key] != null) + return this.options[key]; + + return fallback; +}; + +/** + * Get a config option (as a string). + * @param {String} key + * @param {Object?} fallback + * @returns {String|null} + */ + +Config.prototype.str = function str(key, fallback) { + var value = this.get(key); + + if (fallback === undefined) + fallback = null; + + if (value === null) + return fallback; + + assert(typeof value === 'string', + 'Passed in config option is of wrong type.'); + + return value; +}; + +/** + * Get a config option (as a number). + * @param {String} key + * @param {Object?} fallback + * @returns {Number|null} + */ + +Config.prototype.num = function num(key, fallback) { + var value = this.get(key); + + if (fallback === undefined) + fallback = null; + + if (value === null) + return fallback; + + if (typeof value !== 'string') { + assert(typeof value === 'number', + 'Passed in config option is of wrong type.'); + return value; + } + + value = parseInt(value, 10); + + if (!isFinite(value)) + return fallback; + + return value; +}; + +/** + * Get a config option (as a boolean). + * @param {String} key + * @param {Object?} fallback + * @returns {Boolean|null} + */ + +Config.prototype.bool = function bool(key, fallback) { + var value = this.get(key); + + if (fallback === undefined) + fallback = null; + + if (value === null) + return fallback; + + if (typeof value !== 'string') { + assert(typeof value === 'boolean', + 'Passed in config option is of wrong type.'); + return value; + } + + if (value === 'true' || value === '1') + return true; + + if (value === 'false' || value === '0') + return false; + + return fallback; +}; + +/** + * Get a config option (as a buffer). + * @param {String} key + * @param {Object?} fallback + * @returns {Buffer|null} + */ + +Config.prototype.buf = function buf(key, fallback) { + var value = this.get(key); + + if (fallback === undefined) + fallback = null; + + if (value === null) + return fallback; + + if (typeof value !== 'string') { + assert(Buffer.isBuffer(value), + 'Passed in config option is of wrong type.'); + return value; + } + + return new Buffer(value, 'hex'); +}; + +/** + * Get a config option (as an array of strings). + * @param {String} key + * @param {Object?} fallback + * @returns {String[]|null} + */ + +Config.prototype.list = function list(key, fallback) { + var value = this.get(key); + + if (fallback === undefined) + fallback = null; + + if (value === null) + return fallback; + + if (typeof value !== 'string') { + assert(Array.isArray(value), + 'Passed in config option is of wrong type.'); + return value; + } + + return value.trim().split(/\s*,\s*/); +}; + +/** + * Get a config option (as an object). + * @param {String} key + * @param {Object?} fallback + * @returns {Object|null} + */ + +Config.prototype.obj = function obj(key, fallback) { + var value = this.get(key); + + if (fallback === undefined) + fallback = null; + + if (value === null) + return fallback; + + if (typeof value !== 'string') { + assert(value && typeof value === 'object', + 'Passed in config option is of wrong type.'); + return value; + } + + try { + value = JSON.parse(value); + } catch (e) { + ; + } + + assert(value && typeof value === 'object', + 'Passed in config option is of wrong type.'); + + return value; +}; + +/** + * Get a config option (as a function). + * @param {String} key + * @param {Object?} fallback + * @returns {Function|null} + */ + +Config.prototype.func = function func(key, fallback) { + var value = this.get(key); + + if (fallback === undefined) + fallback = null; + + if (value === null) + return fallback; + + if (typeof value !== 'string') { + assert(value && typeof value === 'function', + 'Passed in config option is of wrong type.'); + return value; + } + + assert(false, + 'Passed in config option is of wrong type.'); +}; + +/** + * Get a config option (as a string). + * @param {String} key + * @param {Object?} fallback + * @returns {String|null} + */ + +Config.prototype.path = function path(key, fallback) { + var value = this.str(key); + + if (fallback === undefined) + fallback = null; + + if (value === null) + return fallback; + + switch (value[0]) { + case '~': // home dir + value = util.HOME + value.substring(1); + break; + case '@': // prefix + value = this.prefix + value.substring(1); + break; + default: // cwd + break; + } + + return util.normalize(value); +}; + +/** + * Get a config option (as a number). + * @param {String} key + * @param {Object?} fallback + * @returns {Number|null} + */ + +Config.prototype.mb = function mb(key, fallback) { + var value = this.num(key); + + if (fallback === undefined) + fallback = null; + + if (value === null) + return fallback; + + return value * 1024; +}; + +/** + * Get a config option (as a bolean or path). + * @param {String} key + * @param {Object?} fallback + * @returns {String|null} + */ + +Config.prototype.boolpath = function boolpath(key, fallback) { + var value = this.get(key); + + if (fallback === undefined) + fallback = null; + + if (value === null) + return fallback; + + if (typeof value !== 'string') { + assert(typeof value === 'boolean' || typeof value === 'string', + 'Passed in config option is of wrong type.'); + return value; + } + + if (value === 'true' || value === '1') + return true; + + if (value === 'false' || value === '0') + return false; + + return this.path(key, fallback); +}; + +/** + * Grab network type from config data. * @private - * @param {Object} data * @returns {String} */ -config.getPrefix = function getPrefix(data) { - var prefix = data.prefix; +Config.prototype.getNetwork = function getNetwork() { + var network = this.str('network'); + + if (!network) + network = 'main'; + + assert(isAlpha(network), 'Bad network.'); + + return network; +}; + +/** + * Grab prefix from config data. + * @private + * @returns {String} + */ + +Config.prototype.getPrefix = function getPrefix() { + var prefix = this.str('prefix'); var network; if (prefix) return prefix; prefix = util.HOME + '/.bcoin'; + network = this.str('network'); - if (data.network) { - assert(isAlpha(data.network), 'Bad network.'); - network = data.network; - } else { - network = 'main'; + if (network) { + assert(isAlpha(network), 'Bad network.'); + if (network !== 'main') + prefix += '/' + network; } - if (network !== 'main') - prefix += '/' + network; - return util.normalize(prefix); }; /** - * Grab config file from data. + * Grab config filename from config data. * @private - * @param {String} prefix - * @param {Object} data * @returns {String} */ -config.getFile = function getFile(prefix, data) { - var file = data.config; +Config.prototype.getFile = function getFile() { + var file = this.str('config'); - if (!file) - return resolve(prefix, 'bcoin.conf'); + if (file) + return file; - return util.normalize(file); -}; - -/** - * Enforce types on parsed data. - * @param {Object} data - * @returns {Object} - */ - -config.toOptions = function toOptions(data) { - var prefix = data.prefix; - var options = {}; - - // 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(prefix); - options.db = str(data.db); - options.maxFiles = num(data.maxfiles); - options.cacheSize = mul(data.cachesize, 1024 * 1024); - - // Logger - options.logLevel = str(data.loglevel); - options.logConsole = bool(data.logconsole); - options.logFile = boolpath(data.logfile, prefix); - - // Chain - options.forceWitness = bool(data.forcewitness); - options.prune = bool(data.prune); - options.checkpoints = bool(data.checkpoints); - options.coinCache = mul(data.coincache, 1024 * 1024); - options.indexTX = bool(data.indextx); - options.indexAddress = bool(data.indexaddress); - - // Mempool - options.mempoolSize = mul(data.mempoolsize, 1000 * 1000); - 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); - options.persistentMempool = bool(data.persistentmempool); - - // Pool - options.selfish = bool(data.selfish); - options.compact = bool(data.compact); - options.bip37 = bool(data.bip37); - options.bip151 = bool(data.bip151); - options.bip150 = bool(data.bip150); - options.identityKey = key(data.identitykey); - options.proxy = str(data.proxy); - options.onion = bool(data.onion); - options.upnp = bool(data.upnp); - options.seeds = list(data.seeds); - options.nodes = list(data.nodes); - options.maxOutbound = num(data.maxoutbound); - options.maxInbound = num(data.maxinbound); - options.publicHost = str(data.publichost); - options.publicPort = num(data.publicport); - options.host = str(data.host); - options.port = num(data.port); - options.listen = bool(data.listen); - options.knownPeers = file(data.knownpeers, prefix, 'utf8'); - options.authPeers = file(data.authpeers, prefix, 'utf8'); - - // Miner - options.payoutAddress = list(data.payoutaddress); - options.coinbaseFlags = str(data.coinbaseflags); - options.preverify = bool(data.preverify); - options.maxBlockWeight = num(data.maxblockweight); - options.reservedBlockWeight = num(data.reservedblockweight); - options.reservedBlockSigops = num(data.reservedblocksigops); - - // HTTP - options.sslCert = file(data.sslcert, prefix); - options.sslKey = file(data.sslkey, prefix); - 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); - - if (options.knownPeers != null) - options.knownPeers = config.parseKnown(options.knownPeers); - - if (options.authPeers != null) - options.authPeers = config.parseAuth(options.authPeers); - - options.raw = data; - - return options; -}; - -/** - * Parse config file. - * @param {String} file - * @returns {Object} - */ - -config.readConfig = function readConfig(file) { - return config.parseConfig(readFile(file)); -}; - -/** - * 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)); + return this.prefix + '/bcoin.conf'; }; /** * Parse config text. + * @private * @param {String} text - * @returns {Object} */ -config.parseConfig = function parseConfig(text) { - var data = Object.create(null); +Config.prototype.parseConfig = function parseConfig(text) { var i, parts, line, key, value, eq, col, alias; assert(typeof text === 'string', 'Config must be text.'); @@ -383,7 +596,7 @@ config.parseConfig = function parseConfig(text) { key = key.replace(/\-/g, '').toLowerCase(); - alias = config.alias.conf[key]; + alias = Config.alias.conf[key]; if (alias) key = alias; @@ -393,21 +606,17 @@ config.parseConfig = function parseConfig(text) { if (value.length === 0) continue; - data[key] = value; + this.data[key] = value; } - - return data; }; /** * Parse arguments. + * @private * @param {Array?} argv - * @returns {Object} */ -config.parseArg = function parseArg(argv) { - var data = Object.create(null); - var args = []; +Config.prototype.parseArg = function parseArg(argv) { var i, j, arg, key, value, alias, equals; if (!argv) @@ -438,11 +647,11 @@ config.parseArg = function parseArg(argv) { if (value.length === 0) continue; - alias = config.alias.arg[key]; + alias = Config.alias.arg[key]; if (alias) key = alias; - data[key] = value; + this.args[key] = value; continue; } @@ -453,10 +662,10 @@ config.parseArg = function parseArg(argv) { for (j = 0; j < arg.length; j++) { key = arg[j]; - alias = config.alias.arg[key]; + alias = Config.alias.arg[key]; if (alias) key = alias; - data[key] = 'true'; + this.args[key] = 'true'; equals = false; } @@ -470,27 +679,23 @@ config.parseArg = function parseArg(argv) { continue; if (key && !equals) { - data[key] = value; + this.args[key] = value; key = null; } else { - args.push(value); + this.argv.push(value); } } - - return { - data: data, - argv: args - }; }; /** * Parse environment variables. - * @param {Object} env + * @private + * @param {Object?} env + * @param {String?} prefix * @returns {Object} */ -config.parseEnv = function parseEnv(env, prefix) { - var data = Object.create(null); +Config.prototype.parseEnv = function parseEnv(env, prefix) { var i, keys, key, value, alias; if (!env) @@ -518,23 +723,21 @@ config.parseEnv = function parseEnv(env, prefix) { if (value.length === 0) continue; - alias = config.alias.env[key]; + alias = Config.alias.env[key]; if (alias) key = alias; - data[key] = value; + this.env[key] = value; } - - return data; }; /** * Parse uri querystring variables. + * @private * @param {String} query - * @returns {Object} */ -config.parseQuery = function parseQuery(query) { +Config.prototype.parseQuery = function parseQuery(query) { if (query == null) { if (!util.isBrowser || !global.location) return {}; @@ -542,16 +745,16 @@ config.parseQuery = function parseQuery(query) { query = global.location.search || ''; } - return config.parseForm(query); + return this.parseForm(query, this.query); }; /** * Parse uri hash variables. + * @private * @param {String} hash - * @returns {Object} */ -config.parseHash = function parseHash(hash) { +Config.prototype.parseHash = function parseHash(hash) { if (hash == null) { if (!util.isBrowser || !global.location) return {}; @@ -559,23 +762,22 @@ config.parseHash = function parseHash(hash) { hash = global.location.hash || ''; } - return config.parseForm(hash); + return this.parseForm(hash, this.hash); }; /** * Parse form-urlencoded variables. + * @private * @param {String} query - * @returns {Object} */ -config.parseForm = function parseForm(query) { - var data = Object.create(null); +Config.prototype.parseForm = function parseForm(query, map) { var i, parts, index, pair, key, value, alias; assert(typeof query === 'string'); if (query.length === 0) - return data; + return; if (query[0] === '?' || query[0] === '#') query = query.substring(1); @@ -605,257 +807,18 @@ config.parseForm = function parseForm(query) { if (value.length === 0) continue; - alias = config.alias.env[key]; + alias = Config.alias.env[key]; if (alias) key = alias; - data[key] = value; + map[key] = value; } - - return 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) { - 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: // cwd - 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) { - if (!value) - return null; - - if (value === 'true' || value === '1') - return true; - - if (value === 'false' || value === '0') - return false; - - return path(value, prefix); -} - -function file(value, prefix, enc) { - if (fs.unsupported) - return null; - - value = path(value, prefix); - - 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, ' '); @@ -878,4 +841,4 @@ function isAlpha(str) { * Expose */ -module.exports = config; +module.exports = Config; diff --git a/lib/node/fullnode.js b/lib/node/fullnode.js index b6b58418..3fcee1ef 100644 --- a/lib/node/fullnode.js +++ b/lib/node/fullnode.js @@ -50,16 +50,16 @@ function FullNode(options) { this.chain = new Chain({ network: this.network, logger: this.logger, - db: this.options.db, - location: this.location('chain'), - maxFiles: this.options.maxFiles, - cacheSize: this.options.cacheSize, - forceWitness: this.options.forceWitness, - prune: this.options.prune, - checkpoints: this.options.checkpoints, - coinCache: this.options.coinCache, - indexTX: this.options.indexTX, - indexAddress: this.options.indexAddress + db: this.config.str('db'), + prefix: this.config.prefix, + maxFiles: this.config.num('max-files'), + cacheSize: this.config.mb('cache-size'), + forceWitness: this.config.bool('force-witness'), + prune: this.config.bool('prune'), + checkpoints: this.config.bool('checkpoints'), + coinCache: this.config.mb('coin-cache'), + indexTX: this.config.bool('index-tx'), + indexAddress: this.config.bool('index-address') }); // Fee estimation. @@ -72,16 +72,16 @@ function FullNode(options) { logger: this.logger, chain: this.chain, fees: this.fees, - db: this.options.db, - location: this.location('mempool'), - persistent: this.options.persistentMempool, - maxSize: this.options.mempoolSize, - limitFree: this.options.limitFree, - limitFreeRelay: this.options.limitFreeRelay, - requireStandard: this.options.requireStandard, - rejectInsaneFees: this.options.rejectInsaneFees, - replaceByFee: this.options.replaceByFee, - indexAddress: this.options.indexAddress + db: this.config.str('db'), + prefix: this.config.prefix, + persistent: this.config.bool('persistent-mempool'), + maxSize: this.config.num('mempool-size'), + limitFree: this.config.bool('limit-free'), + limitFreeRelay: this.config.num('limit-free-relay'), + requireStandard: this.config.bool('require-standard'), + rejectAbsurdFees: this.config.bool('reject-absurd-fees'), + replaceByFee: this.config.bool('replace-by-fee'), + indexAddress: this.config.bool('index-address') }); // Pool needs access to the chain and mempool. @@ -90,26 +90,25 @@ function FullNode(options) { logger: this.logger, chain: this.chain, mempool: this.mempool, - selfish: this.options.selfish, - compact: this.options.compact, - bip37: this.options.bip37, - bip151: this.options.bip151, - bip150: this.options.bip150, - authPeers: this.options.authPeers, - knownPeers: this.options.knownPeers, - identityKey: this.options.identityKey, - maxOutbound: this.options.maxOutbound, - maxInbound: this.options.maxInbound, - proxy: this.options.proxy, - onion: this.options.onion, - upnp: this.options.upnp, - seeds: this.options.seeds, - nodes: this.options.nodes, - publicHost: this.options.publicHost, - publicPort: this.options.publicPort, - host: this.options.host, - port: this.options.port, - listen: this.options.listen + prefix: this.config.prefix, + selfish: this.config.bool('selfish'), + compact: this.config.bool('compact'), + bip37: this.config.bool('bip37'), + bip151: this.config.bool('bip151'), + bip150: this.config.bool('bip150'), + identityKey: this.config.buf('identity-key'), + maxOutbound: this.config.num('max-outbound'), + maxInbound: this.config.num('max-inbound'), + proxy: this.config.str('proxy'), + onion: this.config.bool('onion'), + upnp: this.config.bool('upnp'), + seeds: this.config.list('seeds'), + nodes: this.config.list('nodes'), + publicHost: this.config.str('public-host'), + publicPort: this.config.num('public-port'), + host: this.config.str('host'), + port: this.config.num('port'), + listen: this.config.bool('listen') }); // Miner needs access to the chain and mempool. @@ -119,12 +118,12 @@ function FullNode(options) { chain: this.chain, mempool: this.mempool, fees: this.fees, - address: this.options.payoutAddress, - coinbaseFlags: this.options.coinbaseFlags, - preverify: this.options.preverify, - maxWeight: this.options.maxBlockWeight, - reservedWeight: this.options.reservedBlockWeight, - reservedSigops: this.options.reservedBlockSigops + address: this.config.list('payout-address'), + coinbaseFlags: this.config.str('coinbase-flags'), + preverify: this.config.bool('preverify'), + maxWeight: this.config.num('max-weight'), + reservedWeight: this.config.num('reserved-weight'), + reservedSigops: this.config.num('reserved-sigops') }); // Wallet database needs access to fees. @@ -132,14 +131,14 @@ function FullNode(options) { network: this.network, logger: this.logger, client: this.client, - db: this.options.db, - location: this.location('walletdb'), - maxFiles: this.options.walletMaxFiles, - cacheSize: this.options.walletCacheSize, + db: this.config.str('db'), + prefix: this.config.prefix, + maxFiles: this.config.num('walletdb-max-files'), + cacheSize: this.config.mb('walletdb-cache-size'), witness: false, - checkpoints: this.options.checkpoints, - startHeight: this.options.startHeight, - wipeNoReally: this.options.wipeNoReally, + checkpoints: this.config.bool('checkpoints'), + startHeight: this.config.num('start-height'), + wipeNoReally: this.config.bool('wipe-no-really'), verify: false }); @@ -149,14 +148,16 @@ function FullNode(options) { network: this.network, logger: this.logger, node: this, - key: this.options.sslKey, - cert: this.options.sslCert, - port: this.options.httpPort, - host: this.options.httpHost, - apiKey: this.options.apiKey, - serviceKey: this.options.serviceKey, - walletAuth: this.options.walletAuth, - noAuth: this.options.noAuth + prefix: this.config.prefix, + ssl: this.config.bool('ssl'), + keyFile: this.config.path('ssl-key'), + certFile: this.config.path('ssl-cert'), + host: this.config.str('http-host'), + port: this.config.num('http-port'), + apiKey: this.config.str('api-key'), + serviceKey: this.config.str('service-key'), + walletAuth: this.config.bool('wallet-auth'), + noAuth: this.config.bool('no-auth') }); } diff --git a/lib/node/index.js b/lib/node/index.js index e318a666..431114fa 100644 --- a/lib/node/index.js +++ b/lib/node/index.js @@ -4,7 +4,7 @@ * @module node */ -exports.config = require('./config'); +exports.Config = require('./config'); exports.FullNode = require('./fullnode'); exports.Logger = require('./logger'); exports.Node = require('./node'); diff --git a/lib/node/logger.js b/lib/node/logger.js index c2abad83..9b1d3a6f 100644 --- a/lib/node/logger.js +++ b/lib/node/logger.js @@ -7,8 +7,9 @@ 'use strict'; var assert = require('assert'); -var fs = require('fs'); +var fs = require('../utils/fs'); var util = require('../utils/util'); +var co = require('../utils/co'); /** * Basic stdout and file logger. @@ -28,8 +29,6 @@ function Logger(options) { this.console = true; this.filename = null; this.stream = null; - this.closed = false; - this.lastFail = 0; this.init(options); } @@ -119,39 +118,84 @@ Logger.prototype.init = function init(options) { assert(typeof options.filename === 'string', 'Bad file.'); this.filename = options.filename; } - - if (options.stream != null) { - assert(typeof options.stream === 'object', 'Bad stream.'); - assert(typeof options.stream.write === 'function', 'Bad stream.'); - this.stream = options.stream; - } }; /** * Open the logger. */ -Logger.prototype.open = function open() { - this.closed = false; - if (this.stream) - this.stream.open(); -}; +Logger.prototype.open = co(function* open() { + if (fs.unsupported) + return; + + if (!this.filename) + return; + + this.stream = yield openStream(this.filename); + this.stream.once('error', this.handleError.bind(this)); +}); /** * Destroy the write stream. */ -Logger.prototype.close = function close() { - if (!this.stream) - return; +Logger.prototype.close = co(function* close() { + if (this.timer != null) { + co.clearTimeout(this.timer); + this.timer = null; + } + if (this.stream) { + yield closeStream(this.stream); + this.stream = null; + } +}); + +/** + * Handle write stream error. + * @param {Error} err + */ + +Logger.prototype.handleError = function handleError(err) { try { this.stream.close(); } catch (e) { ; } - this.closed = true; + this.stream = null; + this.retry(); +}; + +/** + * Try to reopen the logger. + * @method + * @private + * @returns {Promise} + */ + +Logger.prototype.reopen = co(function* reopen() { + try { + this.stream = yield openStream(this.filename); + } catch (e) { + this.retry(); + return; + } + this.stream.once('error', this.handleError.bind(this)); +}); + +/** + * Try to reopen the logger after a timeout. + * @method + * @private + * @returns {Promise} + */ + +Logger.prototype.retry = function* retry() { + this.timer = co.setTimeout(function() { + this.timer = null; + this.reopen(); + }, 10000, this); }; /** @@ -338,60 +382,6 @@ Logger.prototype.writeConsole = function writeConsole(level, args) { : process.stdout.write(msg + '\n'); }; -/** - * Create or get the current file stream. - * @returns {Object} - */ - -Logger.prototype.getStream = function getStream() { - var self = this; - - if (this.closed) - return; - - if (this.stream) - return this.stream; - - if (!this.filename) - return; - - if (fs.unsupported) - return; - - if (this.lastFail > util.now() - 10) - return; - - this.lastFail = 0; - - try { - util.mkdir(this.filename, true); - } catch (e) { - this.writeConsole(Logger.levels.WARNING, - ['Could not create log directory.']); - this.writeConsole(Logger.levels.ERROR, [e.message]); - this.lastFail = util.now(); - return; - } - - this.stream = fs.createWriteStream(this.filename, { flags: 'a' }); - - this.stream.on('error', function(err) { - self.writeConsole(Logger.levels.WARNING, - ['Log file stream died!']); - self.writeConsole(Logger.levels.ERROR, [err.message]); - - try { - self.stream.close(); - } catch (e) { - ; - } - - // Retry in ten seconds. - self.stream = null; - self.lastFail = util.now(); - }); -}; - /** * Write a string to the output stream (usually a file). * @param {String} level @@ -400,7 +390,7 @@ Logger.prototype.getStream = function getStream() { Logger.prototype.writeStream = function writeStream(level, args) { var name = Logger.levelsByVal[level]; - var stream = this.getStream(); + var stream = this.stream; var prefix, msg; assert(name, 'Invalid log level.'); @@ -469,6 +459,65 @@ Logger.prototype.memory = function memory() { Logger.global = new Logger(); +/* + * Helpers + */ + +function openStream(filename) { + return new Promise(function(resolve, reject) { + var stream = fs.createWriteStream(filename, { flags: 'a' }); + + function onError(err) { + try { + stream.close(); + } catch (e) { + ; + } + cleanup(); + reject(err); + } + + function onOpen() { + cleanup(); + resolve(stream); + } + + function cleanup() { + stream.removeListener('error', onError); + stream.removeListener('open', onOpen); + } + + stream.once('error', onError); + stream.once('open', onOpen); + }); +} + +function closeStream(stream) { + return new Promise(function(resolve, reject) { + function onError(err) { + cleanup(); + reject(err); + } + + function onClose() { + cleanup(); + resolve(stream); + } + + function cleanup() { + stream.removeListener('error', onError); + stream.removeListener('close', onClose); + } + + stream.removeAllListeners('error'); + stream.removeAllListeners('close'); + stream.once('error', onError); + stream.once('close', onClose); + + stream.close(); + }); +} + /* * Expose */ diff --git a/lib/node/node.js b/lib/node/node.js index c8b73d64..2e432b4b 100644 --- a/lib/node/node.js +++ b/lib/node/node.js @@ -17,6 +17,8 @@ var NodeClient = require('./nodeclient'); var workerPool = require('../workers/workerpool').pool; var ec = require('../crypto/ec'); var native = require('../utils/native'); +var fs = require('../utils/fs'); +var Config = require('./config'); /** * Base class from which every other @@ -33,11 +35,12 @@ function Node(options) { AsyncObject.call(this); - this.options = {}; - this.network = Network.primary; - this.prefix = util.HOME + '/.bcoin'; + this.config = new Config(options); + this.network = Network.get(this.config.network); this.startTime = -1; this.bound = []; + this.plugins = {}; + this.stack = []; this.logger = new Logger(); this.chain = null; @@ -50,7 +53,7 @@ function Node(options) { this.http = null; this.client = null; - this.init(options); + this.init(); } util.inherits(Node, AsyncObject); @@ -61,49 +64,20 @@ util.inherits(Node, AsyncObject); * @param {Object} options */ -Node.prototype.initOptions = function initOptions(options) { - if (!options) - return; +Node.prototype.initOptions = function initOptions() { + var config = this.config; - assert(typeof options === 'object'); + if (config.has('logger')) + this.logger = config.obj('logger'); - this.options = options; + if (config.bool('log-file')) + this.logger.setFile(this.location('debug.log')); - if (options.network != null) { - this.network = Network.get(options.network); - if (this.network !== Network.main) - this.prefix += '/' + this.network.type; - } + if (config.has('log-level')) + this.logger.setLevel(config.str('log-level')); - if (options.prefix != null) { - assert(typeof options.prefix === 'string'); - this.prefix = util.normalize(options.prefix); - } - - if (options.logger != null) { - assert(typeof options.logger === 'object'); - this.logger = options.logger; - } - - if (options.logFile != null) { - if (typeof options.logFile === 'string') { - this.logger.setFile(options.logFile); - } else { - assert(typeof options.logFile === 'boolean'); - if (options.logFile) - this.logger.setFile(this.location('debug.log')); - } - } - - if (options.logLevel != null) { - assert(typeof options.logLevel === 'string'); - this.logger.setLevel(options.logLevel); - } - - if (options.logConsole != null) { - assert(typeof options.logConsole === 'boolean'); - this.logger.console = options.logConsole; - } + if (config.has('log-console')) + this.logger.console = config.bool('log-console'); }; /** @@ -112,24 +86,25 @@ Node.prototype.initOptions = function initOptions(options) { * @param {Object} options */ -Node.prototype.init = function init(options) { +Node.prototype.init = function init() { var self = this; - this.initOptions(options); + this.initOptions(); + this.loadPlugins(); // Local client for walletdb this.client = new NodeClient(this); - this.on('preopen', function() { - self.handlePreopen(); + this.hook('preopen', function() { + return self.handlePreopen(); }); - this.on('open', function() { - self.handleOpen(); + this.hook('open', function() { + return self.handleOpen(); }); - this.on('close', function() { - self.handleClose(); + this.hook('close', function() { + return self.handleClose(); }); }; @@ -138,10 +113,11 @@ Node.prototype.init = function init(options) { * @private */ -Node.prototype.handlePreopen = function handlePreopen() { +Node.prototype.handlePreopen = co(function* handlePreopen() { var self = this; - this.logger.open(); + yield fs.mkdirp(this.config.prefix); + yield this.logger.open(); this.bind(this.network.time, 'offset', function(offset) { self.logger.info('Time offset: %d (%d minutes).', offset, offset / 60 | 0); @@ -173,14 +149,14 @@ Node.prototype.handlePreopen = function handlePreopen() { } self.emit('error', err); }); -}; +}); /** * Open node. * @private */ -Node.prototype.handleOpen = function handleOpen() { +Node.prototype.handleOpen = co(function* handleOpen() { this.startTime = util.now(); if (!ec.binding) { @@ -197,19 +173,21 @@ Node.prototype.handleOpen = function handleOpen() { this.logger.warning('Warning: worker pool is disabled.'); this.logger.warning('Verification will be slow.'); } -}; + + yield this.openPlugins(); +}); /** * Close node. Unbind all events. * @private */ -Node.prototype.handleClose = function handleClose() { +Node.prototype.handleClose = co(function* handleClose() { var i, bound; - this.startTime = -1; + yield this.closePlugins(); - this.logger.close(); + this.startTime = -1; for (i = 0; i < this.bound.length; i++) { bound = this.bound[i]; @@ -217,7 +195,9 @@ Node.prototype.handleClose = function handleClose() { } this.bound.length = 0; -}; + + yield this.logger.close(); +}); /** * Bind to an event on `obj`, save listener for removal. @@ -269,7 +249,7 @@ Node.prototype.error = function error(err) { */ Node.prototype.location = function location(name) { - return this.prefix + '/' + name; + return this.config.prefix + '/' + name; }; /** @@ -296,7 +276,7 @@ Node.prototype.openWallet = co(function* openWallet() { options = { id: 'primary', - passphrase: this.options.passphrase + passphrase: this.config.str('passphrase') }; wallet = yield this.walletdb.ensure(options); @@ -314,6 +294,139 @@ Node.prototype.openWallet = co(function* openWallet() { this.http.rpc.wallet = wallet; }); +/** + * Attach a plugin. + * @param {Object} plugin + */ + +Node.prototype.use = function use(plugin) { + var instance; + + assert(plugin, 'Plugin must be an object.'); + assert(typeof plugin.init === 'function', '`init` must be a function.'); + + assert(!this.loaded, 'Cannot add plugin after node is loaded.'); + + instance = plugin.init(this); + + assert(typeof instance.open === 'function', '`open` must be a function.'); + assert(typeof instance.close === 'function', '`close` must be a function.'); + + if (plugin.name) { + assert(typeof plugin.name === 'string', '`name` must be a string.'); + + // Reserved names + switch (plugin.name) { + case 'logger': + case 'chain': + case 'fees': + case 'mempool': + case 'miner': + case 'pool': + case 'walletdb': + assert(false, plugin.name + ' is already added.'); + break; + } + + assert(!this.plugins[plugin.name], plugin.name + ' is already added.'); + + this.plugins[plugin.name] = instance; + } + + this.stack.push(instance); +}; + +/** + * Require a plugin. + * @param {String} name + * @returns {Object} + */ + +Node.prototype.require = function require(name) { + var plugin; + + assert(typeof name === 'string', 'Plugin name must be a string.'); + + switch (name) { + case 'logger': + assert(this.logger, 'logger is not loaded.'); + return this.logger; + case 'chain': + assert(this.chain, 'chain is not loaded.'); + return this.chain; + case 'fees': + assert(this.fees, 'fees is not loaded.'); + return this.fees; + case 'mempool': + assert(this.mempool, 'mempool is not loaded.'); + return this.mempool; + case 'miner': + assert(this.miner, 'miner is not loaded.'); + return this.miner; + case 'pool': + assert(this.pool, 'pool is not loaded.'); + return this.pool; + case 'walletdb': + assert(this.walletdb, 'walletdb is not loaded.'); + return this.walletdb; + } + + plugin = this.plugins[name]; + assert(plugin, name + ' is not loaded.'); + + return plugin; +}; + +/** + * Load plugins. + * @private + */ + +Node.prototype.loadPlugins = function loadPlugins() { + var plugins = this.config.list('plugins', []); + var loader = this.config.func('loader'); + var i, name, plugin; + + if (!loader) + return; + + for (i = 0; i < plugins.length; i++) { + name = plugins[i]; + assert(typeof name === 'string', + 'Plugin name must be a string.'); + plugin = loader(name); + this.use(plugin); + } +}; + +/** + * Open plugins. + * @private + */ + +Node.prototype.openPlugins = co(function* openPlugins() { + var i, plugin; + + for (i = 0; i < this.stack.length; i++) { + plugin = this.stack[i]; + yield plugin.open(); + } +}); + +/** + * Close plugins. + * @private + */ + +Node.prototype.closePlugins = co(function* closePlugins() { + var i, plugin; + + for (i = 0; i < this.stack.length; i++) { + plugin = this.stack[i]; + yield plugin.close(); + } +}); + /* * Expose */ diff --git a/lib/node/spvnode.js b/lib/node/spvnode.js index 1486a488..4652280e 100644 --- a/lib/node/spvnode.js +++ b/lib/node/spvnode.js @@ -47,12 +47,12 @@ function SPVNode(options) { this.chain = new Chain({ network: this.network, logger: this.logger, - db: this.options.db, - location: this.location('spvchain'), - maxFiles: this.options.maxFiles, - cacheSize: this.options.cacheSize, - forceWitness: this.options.forceWitness, - checkpoints: this.options.checkpoints, + db: this.config.str('db'), + prefix: this.config.prefix, + maxFiles: this.config.num('max-files'), + cacheSize: this.config.mb('cache-size'), + forceWitness: this.config.bool('force-witness'), + checkpoints: this.config.bool('checkpoints'), spv: true }); @@ -60,17 +60,16 @@ function SPVNode(options) { network: this.network, logger: this.logger, chain: this.chain, - proxy: this.options.proxy, - onion: this.options.onion, - upnp: this.options.upnp, - seeds: this.options.seeds, - nodes: this.options.nodes, - bip151: this.options.bip151, - bip150: this.options.bip150, - authPeers: this.options.authPeers, - knownPeers: this.options.knownPeers, - identityKey: this.options.identityKey, - maxOutbound: this.options.maxOutbound, + prefix: this.config.prefix, + proxy: this.config.str('proxy'), + onion: this.config.bool('onion'), + upnp: this.config.bool('upnp'), + seeds: this.config.list('seeds'), + nodes: this.config.list('nodes'), + bip151: this.config.bool('bip151'), + bip150: this.config.bool('bip150'), + identityKey: this.config.buf('identity-key'), + maxOutbound: this.config.num('max-outbound'), selfish: true, listen: false }); @@ -79,14 +78,14 @@ function SPVNode(options) { network: this.network, logger: this.logger, client: this.client, - db: this.options.db, - location: this.location('walletdb'), - maxFiles: this.options.walletMaxFiles, - cacheSize: this.options.walletCacheSize, + db: this.config.str('db'), + prefix: this.config.prefix, + maxFiles: this.config.num('max-files'), + cacheSize: this.config.mb('cache-size'), witness: false, - checkpoints: this.options.checkpoints, - startHeight: this.options.startHeight, - wipeNoReally: this.options.wipeNoReally, + checkpoints: this.config.bool('checkpoints'), + startHeight: this.config.num('start-height'), + wipeNoReally: this.config.bool('wipe-no-really'), verify: true, spv: true }); @@ -96,14 +95,16 @@ function SPVNode(options) { network: this.network, logger: this.logger, node: this, - key: this.options.sslKey, - cert: this.options.sslCert, - port: this.options.httpPort, - host: this.options.httpHost, - apiKey: this.options.apiKey, - serviceKey: this.options.serviceKey, - walletAuth: this.options.walletAuth, - noAuth: this.options.noAuth + prefix: this.config.prefix, + ssl: this.config.bool('ssl'), + keyFile: this.config.path('ssl-key'), + certFile: this.config.path('ssl-cert'), + host: this.config.str('host'), + port: this.config.num('port'), + apiKey: this.config.str('api-key'), + serviceKey: this.config.str('service-key'), + walletAuth: this.config.bool('wallet-auth'), + noAuth: this.config.bool('no-auth') }); } diff --git a/lib/pkg.js b/lib/pkg.js index 7c1c856e..fc02c54a 100644 --- a/lib/pkg.js +++ b/lib/pkg.js @@ -19,3 +19,12 @@ exports.version = 'v1.0.0-beta.9'; */ exports.url = 'https://github.com/bcoin-org/bcoin'; + +/** + * Default prefix. + * @const {String} + */ + +exports.prefix = process.platform === 'win32' + ? 'C:/Temp/.bcoin' + : '/tmp/.bcoin'; diff --git a/lib/utils/asyncobject.js b/lib/utils/asyncobject.js index 766f0632..be6fdd32 100644 --- a/lib/utils/asyncobject.js +++ b/lib/utils/asyncobject.js @@ -63,7 +63,7 @@ AsyncObject.prototype.__open = co(function* open() { if (this.loaded) return; - this.emit('preopen'); + yield this.fire('preopen'); this.loading = true; @@ -78,7 +78,7 @@ AsyncObject.prototype.__open = co(function* open() { this.loading = false; this.loaded = true; - this.emit('open'); + yield this.fire('open'); }); /** @@ -107,7 +107,7 @@ AsyncObject.prototype.__close = co(function* close() { if (!this.loaded) return; - this.emit('preclose'); + yield this.fire('preclose'); this.closing = true; @@ -122,7 +122,7 @@ AsyncObject.prototype.__close = co(function* close() { this.closing = false; this.loaded = false; - this.emit('close'); + yield this.fire('close'); }); /** diff --git a/lib/utils/util.js b/lib/utils/util.js index 50cc8f51..350cacd3 100644 --- a/lib/utils/util.js +++ b/lib/utils/util.js @@ -11,7 +11,6 @@ var assert = require('assert'); var nodeUtil = require('util'); -var fs = require('fs'); var os = require('os'); var Number, Math, Date; @@ -983,81 +982,6 @@ util.normalize = function normalize(path, dirname) { return parts.join('/'); }; -/** - * Create a full directory structure. - * @param {String} path - */ - -util.mkdirp = function mkdirp(path) { - var i, parts, stat; - - if (fs.unsupported) - return; - - path = path.replace(/\\/g, '/'); - path = path.replace(/(^|\/)\.\//, '$1'); - path = path.replace(/\/+\.?$/, ''); - parts = path.split(/\/+/); - path = ''; - - if (process.platform === 'win32') { - if (parts[0].indexOf(':') !== -1) - path = parts.shift() + '/'; - } - - if (parts.length > 0) { - if (parts[0].length === 0) { - parts.shift(); - path = '/'; - } - } - - for (i = 0; i < parts.length; i++) { - path += parts[i]; - - try { - stat = fs.statSync(path); - if (!stat.isDirectory()) - throw new Error('Could not create directory.'); - } catch (e) { - if (e.code === 'ENOENT') - fs.mkdirSync(path, 488 /* 0750 */); - else - throw e; - } - - path += '/'; - } -}; - -/** - * Ensure a directory. - * @param {String} path - * @param {Boolean?} dirname - */ - -util.mkdir = function mkdir(path, dirname) { - if (util.isBrowser) - return; - - path = util.normalize(path, dirname); - - if (util._paths[path]) - return; - - util._paths[path] = true; - - return util.mkdirp(path); -}; - -/** - * Cached mkdirp paths. - * @private - * @type {Object} - */ - -util._paths = {}; - /** * Ensure hidden-class mode for object. * @param {Object} obj diff --git a/lib/wallet/walletdb.js b/lib/wallet/walletdb.js index a6d0eadb..38aa493b 100644 --- a/lib/wallet/walletdb.js +++ b/lib/wallet/walletdb.js @@ -2171,6 +2171,7 @@ function WalletOptions(options) { this.logger = Logger.global; this.client = null; + this.prefix = null; this.location = null; this.db = 'memory'; this.maxFiles = 64; @@ -2212,6 +2213,12 @@ WalletOptions.prototype.fromOptions = function fromOptions(options) { this.client = options.client; } + if (options.prefix != null) { + assert(typeof options.prefix === 'string'); + this.prefix = options.prefix; + this.location = this.prefix + '/walletdb'; + } + if (options.location != null) { assert(typeof options.location === 'string'); this.location = options.location;