diff --git a/lib/blockchain/chain.js b/lib/blockchain/chain.js index 45f56d2b..8fd18013 100644 --- a/lib/blockchain/chain.js +++ b/lib/blockchain/chain.js @@ -2281,7 +2281,7 @@ function ChainOptions(options) { this.bufferKeys = !util.isBrowser; this.spv = false; - this.witness = false; + this.witness = this.network.witness; this.prune = false; this.indexTX = false; this.indexAddress = false; @@ -2307,6 +2307,8 @@ ChainOptions.prototype.fromOptions = function fromOptions(options) { if (options.network != null) this.network = Network.get(options.network); + this.witness = this.network.witness; + if (options.logger != null) { assert(typeof options.logger === 'object'); this.logger = options.logger; diff --git a/lib/http/rpc.js b/lib/http/rpc.js index b21471cf..a7017535 100644 --- a/lib/http/rpc.js +++ b/lib/http/rpc.js @@ -2190,7 +2190,7 @@ RPC.prototype.sendrawtransaction = co(function* sendrawtransaction(args) { tx = TX.fromRaw(args[0], 'hex'); - this.node.sendTX(tx).catch(util.nop); + this.node.relay(tx); return tx.txid(); }); diff --git a/lib/net/pool.js b/lib/net/pool.js index 88ff3721..251a2460 100644 --- a/lib/net/pool.js +++ b/lib/net/pool.js @@ -1927,7 +1927,7 @@ function PoolOptions(options) { this.chain = null; this.mempool = null; - this.witness = false; + this.witness = this.network.witness; this.spv = false; this.listen = false; this.headers = false; diff --git a/lib/node/config.js b/lib/node/config.js index 03331e4a..0bc245e0 100644 --- a/lib/node/config.js +++ b/lib/node/config.js @@ -227,6 +227,14 @@ config.parseData = function parseData(data, prefix, dirname) { 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; }; diff --git a/lib/node/fullnode.js b/lib/node/fullnode.js index 87f1a87b..252ddf23 100644 --- a/lib/node/fullnode.js +++ b/lib/node/fullnode.js @@ -175,7 +175,7 @@ util.inherits(FullNode, Node); FullNode.prototype._init = function _init() { var self = this; - var onError = this._error.bind(this); + var onError = this.error.bind(this); // Bind to errors this.chain.on('error', onError); @@ -201,7 +201,7 @@ FullNode.prototype._init = function _init() { try { yield self.mempool.addBlock(entry, block.txs); } catch (e) { - self._error(e); + self.error(e); } } self.emit('block', block); @@ -213,7 +213,7 @@ FullNode.prototype._init = function _init() { try { yield self.mempool.removeBlock(entry, block.txs); } catch (e) { - self._error(e); + self.error(e); } } self.emit('disconnect', entry, block); @@ -223,7 +223,7 @@ FullNode.prototype._init = function _init() { try { yield self.mempool.reset(); } catch (e) { - self._error(e); + self.error(e); } self.emit('reset', tip); })); @@ -305,11 +305,7 @@ FullNode.prototype.broadcast = co(function* broadcast(item) { }); /** - * Verify a transaction, add it to the mempool, and broadcast. - * Safer than {@link FullNode#broadcast}. - * @example - * node.sendTX(tx, callback); - * node.sendTX(tx, true, callback); + * Add transaction to mempool, broadcast. * @param {TX} tx */ @@ -320,7 +316,7 @@ FullNode.prototype.sendTX = co(function* sendTX(tx) { missing = yield this.mempool.addTX(tx); } catch (err) { if (err.type === 'VerifyError' && err.score === 0) { - this._error(err); + this.error(err); this.logger.warning('Verification failed for tx: %s.', tx.txid()); this.logger.warning('Attempting to broadcast anyway...'); this.broadcast(tx); @@ -338,10 +334,24 @@ FullNode.prototype.sendTX = co(function* sendTX(tx) { // We need to announce by hand if // we're running in selfish mode. - if (this.options.selfish) + if (this.pool.options.selfish) this.pool.announceTX(tx); }); +/** + * Add transaction to mempool, broadcast. Silence errors. + * @param {TX} tx + * @returns {Promise} + */ + +FullNode.prototype.relay = co(function* relay(tx) { + try { + yield this.sendTX(tx); + } catch (e) { + this.error(e); + } +}); + /** * Connect to the network. * @returns {Promise} diff --git a/lib/node/logger.js b/lib/node/logger.js index cb95550b..f7378750 100644 --- a/lib/node/logger.js +++ b/lib/node/logger.js @@ -6,9 +6,9 @@ 'use strict'; -var util = require('../utils/util'); var assert = require('assert'); var fs = require('fs'); +var util = require('../utils/util'); /** * Basic stdout and file logger. @@ -23,14 +23,15 @@ function Logger(options) { if (!(this instanceof Logger)) return new Logger(options); - this.level = Logger.levels.WARNING; + this.level = Logger.levels.NONE; this.colors = Logger.HAS_TTY; this.console = true; - this.file = null; + this.filename = null; this.stream = null; this.closed = false; + this.lastFail = 0; - this._init(options); + this.init(options); } /** @@ -56,7 +57,8 @@ Logger.levels = { /** * Available log levels. - * @enum {Number} + * @const {String[]} + * @default */ Logger.levelsByVal = [ @@ -70,7 +72,8 @@ Logger.levelsByVal = [ /** * Default CSI colors. - * @enum {String} + * @const {String[]} + * @default */ Logger.colors = [ @@ -88,7 +91,7 @@ Logger.colors = [ * @param {Object} options */ -Logger.prototype._init = function _init(options) { +Logger.prototype.init = function init(options) { if (!options) return; @@ -112,9 +115,9 @@ Logger.prototype._init = function _init(options) { this.console = options.console; } - if (options.file != null) { - assert(typeof options.file === 'string', 'Bad file.'); - this.file = options.file; + if (options.filename != null) { + assert(typeof options.filename === 'string', 'Bad file.'); + this.filename = options.filename; } if (options.stream != null) { @@ -151,6 +154,17 @@ Logger.prototype.close = function close() { this.closed = true; }; +/** + * Set the log file location. + * @param {String} filename + */ + +Logger.prototype.setFile = function setFile(filename) { + assert(typeof filename === 'string'); + assert(!this.stream, 'Log stream has already been created.'); + this.filename = filename; +}; + /** * Set or reset the log level. * @param {String} level @@ -324,6 +338,49 @@ 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() { + if (this.closed) + return; + + if (!this.filename) + return; + + if (fs.unsupported) + return; + + if (this.stream) + return this.stream; + + if (this.lastFail > util.now() - 10) + return; + + this.lastFail = 0; + + util.mkdir(this.filename, true); + + 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 @@ -332,26 +389,14 @@ Logger.prototype.writeConsole = function writeConsole(level, args) { Logger.prototype.writeStream = function writeStream(level, args) { var name = Logger.levelsByVal[level]; + var stream = this.getStream(); var prefix, msg; assert(name, 'Invalid log level.'); - if (this.closed) + if (!stream) return; - if (!this.stream) { - if (!this.file) - return; - - if (fs.unsupported) - return; - - util.mkdir(this.file, true); - - this.stream = fs.createWriteStream(this.file, { flags: 'a' }); - this.stream.on('error', function() {}); - } - prefix = '[' + name + '] '; msg = prefix + util.format(args, false); msg = '(' + util.date() + '): ' + msg + '\n'; @@ -359,7 +404,7 @@ Logger.prototype.writeStream = function writeStream(level, args) { if (!util.isBrowser) msg = process.pid + ' ' + msg; - this.stream.write(msg); + stream.write(msg); }; /** @@ -411,7 +456,7 @@ Logger.prototype.memory = function memory() { * Default */ -Logger.global = new Logger('none'); +Logger.global = new Logger(); /* * Expose diff --git a/lib/node/node.js b/lib/node/node.js index 933ec3c7..36f34ab9 100644 --- a/lib/node/node.js +++ b/lib/node/node.js @@ -7,10 +7,10 @@ 'use strict'; +var assert = require('assert'); var AsyncObject = require('../utils/async'); var util = require('../utils/util'); var co = require('../utils/co'); -var assert = require('assert'); var Network = require('../protocol/network'); var Logger = require('./logger'); var NodeClient = require('./nodeclient'); @@ -31,16 +31,13 @@ function Node(options) { AsyncObject.call(this); - if (!options) - options = {}; + this.options = {}; + this.network = Network.primary; + this.prefix = util.HOME + '/.bcoin'; + this.startTime = -1; + this.bound = []; - this.parseOptions(options); - - this.options = options; - this.network = Network.get(options.network); - this.prefix = options.prefix; - - this.logger = options.logger; + this.logger = new Logger(); this.chain = null; this.fees = null; this.mempool = null; @@ -49,37 +46,77 @@ function Node(options) { this.walletdb = null; this.wallet = null; this.http = null; + this.client = null; - // Local client for walletdb - this.client = new NodeClient(this); - - this.startTime = -1; - - this._bound = []; - - this.__init(); + this.init(options); } util.inherits(Node, AsyncObject); /** - * Initialize node. + * Initialize options. * @private + * @param {Object} options */ -Node.prototype.__init = function __init() { - var self = this; +Node.prototype.initOptions = function initOptions(options) { + if (!options) + return; - if (!this.logger) { - this.logger = new Logger({ - level: this.options.logLevel || 'none', - console: this.options.logConsole, - file: this.options.logFile - }); + assert(typeof options === 'object'); + + this.options = options; + + if (options.network != null) + this.network = Network.get(options.network); + + 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; + } +}; + +/** + * Initialize node. + * @private + * @param {Object} options + */ + +Node.prototype.init = function init(options) { + var self = this; + + this.initOptions(options); + + // Local client for walletdb + this.client = new NodeClient(this); + this.on('preopen', function() { - self._onOpen(); + self.handleOpen(); }); this.on('open', function() { @@ -88,7 +125,7 @@ Node.prototype.__init = function __init() { this.on('close', function() { self.startTime = -1; - self._onClose(); + self.handleClose(); }); }; @@ -97,33 +134,35 @@ Node.prototype.__init = function __init() { * @private */ -Node.prototype._onOpen = function _onOpen() { +Node.prototype.handleOpen = function handleOpen() { var self = this; this.logger.open(); - this._bind(this.network.time, 'offset', function(offset) { + this.bind(this.network.time, 'offset', function(offset) { self.logger.info('Time offset: %d (%d minutes).', offset, offset / 60 | 0); }); - this._bind(this.network.time, 'sample', function(sample, total) { - self.logger.debug('Added time data: samples=%d, offset=%d (%d minutes).', + this.bind(this.network.time, 'sample', function(sample, total) { + self.logger.debug( + 'Added time data: samples=%d, offset=%d (%d minutes).', total, sample, sample / 60 | 0); }); - this._bind(this.network.time, 'mismatch', function() { + this.bind(this.network.time, 'mismatch', function() { + self.logger.warning('Adjusted time mismatch!'); self.logger.warning('Please make sure your system clock is correct!'); }); - this._bind(workerPool, 'spawn', function(child) { + this.bind(workerPool, 'spawn', function(child) { self.logger.info('Spawning worker process: %d.', child.id); }); - this._bind(workerPool, 'exit', function(code, child) { + this.bind(workerPool, 'exit', function(code, child) { self.logger.warning('Worker %d exited: %s.', child.id, code); }); - this._bind(workerPool, 'error', function(err, child) { + this.bind(workerPool, 'error', function(err, child) { if (child) { self.logger.error('Worker %d error: %s', child.id, err.message); return; @@ -137,17 +176,17 @@ Node.prototype._onOpen = function _onOpen() { * @private */ -Node.prototype._onClose = function _onClose() { +Node.prototype.handleClose = function handleClose() { var i, bound; this.logger.close(); - for (i = 0; i < this._bound.length; i++) { - bound = this._bound[i]; + for (i = 0; i < this.bound.length; i++) { + bound = this.bound[i]; bound[0].removeListener(bound[1], bound[2]); } - this._bound.length = 0; + this.bound.length = 0; }; /** @@ -158,8 +197,8 @@ Node.prototype._onClose = function _onClose() { * @param {Function} listener */ -Node.prototype._bind = function _bind(obj, event, listener) { - this._bound.push([obj, event, listener]); +Node.prototype.bind = function bind(obj, event, listener) { + this.bound.push([obj, event, listener]); obj.on(event, listener); }; @@ -169,7 +208,7 @@ Node.prototype._bind = function _bind(obj, event, listener) { * @param {Error} err */ -Node.prototype._error = function _error(err) { +Node.prototype.error = function error(err) { if (!err) return; @@ -192,48 +231,6 @@ Node.prototype._error = function _error(err) { this.emit('error', err); }; -/** - * Parse options object. - * @private - * @param {Object} options - * @returns {Object} - */ - -Node.prototype.parseOptions = function parseOptions(options) { - options.network = Network.get(options.network); - - if (!options.prefix) - options.prefix = util.HOME + '/.bcoin'; - - if (!options.db) - options.db = 'memory'; - - options.prefix = util.normalize(options.prefix); - - if (options.logFile && typeof options.logFile !== 'string') { - options.logFile = options.prefix; - if (options.network.type !== 'main') - options.logFile += '/' + options.network.type; - options.logFile += '/debug.log'; - } - - options.logFile = options.logFile - ? util.normalize(options.logFile) - : null; - - if (options.fast) { - options.headers = true; - options.useCheckpoints = true; - options.cacheSize = 300 << 20; - options.coinCache = 100 << 20; - } - - if (options.witness == null) - options.witness = options.network.witness; - - return options; -}; - /** * Create a file path from a name * as well as the node's prefix. @@ -243,7 +240,7 @@ Node.prototype.parseOptions = function parseOptions(options) { Node.prototype.location = function location(name) { var path = this.prefix; - if (this.network.type !== 'main') + if (this.network !== Network.main) path += '/' + this.network.type; path += '/' + name; return path; diff --git a/lib/node/nodeclient.js b/lib/node/nodeclient.js index 8e634ca0..edd5aeab 100644 --- a/lib/node/nodeclient.js +++ b/lib/node/nodeclient.js @@ -123,7 +123,7 @@ NodeClient.prototype.getEntry = co(function* getEntry(hash) { */ NodeClient.prototype.send = function send(tx) { - this.node.sendTX(tx).catch(util.nop); + this.node.relay(tx); return Promise.resolve(); }; diff --git a/lib/node/spvnode.js b/lib/node/spvnode.js index e29b3588..d4254a30 100644 --- a/lib/node/spvnode.js +++ b/lib/node/spvnode.js @@ -305,6 +305,16 @@ SPVNode.prototype.sendTX = function sendTX(tx) { return this.broadcast(tx); }; +/** + * Broadcast a transaction. Silence errors. + * @param {TX} tx + * @returns {Promise} + */ + +SPVNode.prototype.relay = function relay(tx) { + return this.broadcast(tx); +}; + /** * Connect to the network. * @returns {Promise} diff --git a/lib/workers/workerpool.js b/lib/workers/workerpool.js index 073a841b..65722e76 100644 --- a/lib/workers/workerpool.js +++ b/lib/workers/workerpool.js @@ -46,6 +46,8 @@ function WorkerPool(options) { this.enabled = true; this.set(options); + + this.on('error', util.nop); } util.inherits(WorkerPool, EventEmitter); @@ -871,13 +873,17 @@ exports.pool = new WorkerPool(); exports.pool.enabled = false; exports.set = function set(options) { - this.pool.set(options); + this.pool.set({ + enabled: options.useWorkers, + size: options.maxWorkers || null, + timeout: options.workerTimeout || null + }); }; exports.set({ useWorkers: +process.env.BCOIN_USE_WORKERS === 1, - maxWorkers: +process.env.BCOIN_MAX_WORKERS || null, - workerTimeout: +process.env.BCOIN_WORKER_TIMEOUT || null + maxWorkers: +process.env.BCOIN_MAX_WORKERS, + workerTimeout: +process.env.BCOIN_WORKER_TIMEOUT }); /*