diff --git a/lib/chain/chain.js b/lib/chain/chain.js index 0c96b072..4d273979 100644 --- a/lib/chain/chain.js +++ b/lib/chain/chain.js @@ -928,6 +928,13 @@ Chain.prototype.reset = co(function* reset(height) { } }); +/** + * Reset the chain to the desired height without a lock. + * @private + * @param {Number} height + * @param {Function} callback + */ + Chain.prototype._reset = co(function* reset(height) { var result = yield this.db.reset(height); @@ -956,6 +963,13 @@ Chain.prototype.resetTime = co(function* resetTime(ts) { } }); +/** + * Reset the chain to the desired timestamp without a lock. + * @private + * @param {Number} ts - Timestamp. + * @param {Function} callback + */ + Chain.prototype._resetTime = co(function* resetTime(ts) { var entry = yield this.byTime(ts); @@ -1003,6 +1017,13 @@ Chain.prototype.add = co(function* add(block) { } }); +/** + * Add a block to the chain without a lock. + * @private + * @param {Block|MerkleBlock|MemBlock} block + * @param {Function} callback - Returns [{@link VerifyError}]. + */ + Chain.prototype._add = co(function* add(block) { var ret, initial, hash, prevBlock; var height, checkpoint, orphan, entry; @@ -1017,7 +1038,8 @@ Chain.prototype._add = co(function* add(block) { hash = block.hash('hex'); prevBlock = block.prevBlock; - this._mark(); + // Mark the start time. + this.mark(); // Do not revalidate known invalid blocks. if (this.invalid[hash] || this.invalid[prevBlock]) { @@ -1176,7 +1198,7 @@ Chain.prototype._add = co(function* add(block) { } // Keep track of stats. - this._done(block, entry); + this.finish(block, entry); // No orphan chain. if (!this.orphan.map[hash]) @@ -1225,7 +1247,7 @@ Chain.prototype._isSlow = function _isSlow() { * @private */ -Chain.prototype._mark = function _mark() { +Chain.prototype.mark = function mark() { this._time = utils.hrtime(); }; @@ -1237,7 +1259,7 @@ Chain.prototype._mark = function _mark() { * @param {ChainEntry} entry */ -Chain.prototype._done = function _done(block, entry) { +Chain.prototype.finish = function finish(block, entry) { var elapsed, time; // Keep track of total blocks handled. @@ -1500,6 +1522,13 @@ Chain.prototype.getLocator = co(function* getLocator(start) { } }); +/** + * Calculate chain locator without a lock. + * @private + * @param {(Number|Hash)?} start + * @param {Function} callback + */ + Chain.prototype._getLocator = co(function* getLocator(start) { var hashes = []; var step = 1; diff --git a/lib/mempool/mempool.js b/lib/mempool/mempool.js index 59d4aa09..43ba9c81 100644 --- a/lib/mempool/mempool.js +++ b/lib/mempool/mempool.js @@ -154,6 +154,14 @@ Mempool.prototype.addBlock = co(function* addBlock(block) { } }); +/** + * Notify the mempool that a new block + * has come without a lock. + * @private + * @param {Block} block + * @param {Function} callback + */ + Mempool.prototype._addBlock = co(function* addBlock(block) { var entries = []; var i, entry, tx, hash; @@ -207,6 +215,14 @@ Mempool.prototype.removeBlock = co(function* removeBlock(block) { } }); +/** + * Notify the mempool that a block + * has been disconnected without a lock. + * @private + * @param {Block} block + * @param {Function} callback + */ + Mempool.prototype._removeBlock = co(function* removeBlock(block) { var i, entry, tx, hash; @@ -553,7 +569,7 @@ Mempool.prototype.addTX = co(function* addTX(tx) { }); /** - * Add a transaction to the mempool. + * Add a transaction to the mempool without a lock. * @private * @param {TX} tx * @param {Function} callback - Returns [{@link VerifyError}]. @@ -688,6 +704,13 @@ Mempool.prototype.addUnchecked = co(function* addUnchecked(entry) { } }); +/** + * Add a transaction to the mempool without a lock. + * @private + * @param {MempoolEntry} entry + * @param {Function} callback - Returns [{@link VerifyError}]. + */ + Mempool.prototype._addUnchecked = co(function* addUnchecked(entry) { var i, resolved, tx, orphan; diff --git a/lib/net/bip150.js b/lib/net/bip150.js index b414e9ed..63dd2220 100644 --- a/lib/net/bip150.js +++ b/lib/net/bip150.js @@ -11,6 +11,7 @@ var EventEmitter = require('events').EventEmitter; var bcoin = require('../env'); var utils = require('../utils/utils'); +var spawn = require('../utils/spawn'); var crypto = require('../crypto/crypto'); var packets = require('./packets'); var assert = utils.assert; @@ -247,7 +248,14 @@ BIP150.prototype.complete = function complete(err) { this.callback = null; }; -BIP150.prototype.wait = function wait(timeout, callback) { +BIP150.prototype.wait = function wait(timeout) { + var self = this; + return new Promise(function(resolve, reject) { + self._wait(timeout, spawn.wrap(resolve, reject)); + }); +}; + +BIP150.prototype._wait = function wait(timeout, callback) { var self = this; assert(!this.auth, 'Cannot wait for init after handshake.'); diff --git a/lib/net/bip151.js b/lib/net/bip151.js index 00d39e87..31a2c0f9 100644 --- a/lib/net/bip151.js +++ b/lib/net/bip151.js @@ -15,6 +15,7 @@ var EventEmitter = require('events').EventEmitter; var bcoin = require('../env'); var utils = require('../utils/utils'); +var spawn = require('../utils/spawn'); var crypto = require('../crypto/crypto'); var assert = utils.assert; var constants = bcoin.constants; @@ -455,7 +456,19 @@ BIP151.prototype.complete = function complete(err) { * @param {Function} callback */ -BIP151.prototype.wait = function wait(timeout, callback) { +BIP151.prototype.wait = function wait(timeout) { + var self = this; + return new Promise(function(resolve, reject) { + self._wait(timeout, spawn.wrap(resolve, reject)); + }); +}; + +/** + * Set a timeout and wait for handshake to complete. + * @private + */ + +BIP151.prototype._wait = function wait(timeout, callback) { var self = this; assert(!this.handshake, 'Cannot wait for init after handshake.'); diff --git a/lib/net/bip152.js b/lib/net/bip152.js index 610f1610..f6853b58 100644 --- a/lib/net/bip152.js +++ b/lib/net/bip152.js @@ -39,6 +39,7 @@ function CompactBlock(options) { this.count = 0; this.sipKey = null; this.timeout = null; + this.callback = null; if (options) this.fromOptions(options); @@ -362,12 +363,31 @@ CompactBlock.fromBlock = function fromBlock(block, nonce) { return new CompactBlock().fromBlock(block, nonce); }; -CompactBlock.prototype.startTimeout = function startTimeout(time, callback) { - assert(this.timeout == null); - this.timeout = setTimeout(callback, time); +CompactBlock.prototype.wait = function wait(callback) { + var self = this; + return new Promise(function(resolve, reject) { + self._wait(time, spawn.wrap(resolve, reject)); + }); }; -CompactBlock.prototype.stopTimeout = function stopTimeout() { +CompactBlock.prototype._wait = function wait(time, callback) { + var self = this; + assert(this.timeout == null); + this.callback = callback; + this.timeout = setTimeout(function() { + self.complete(new Error('Timed out.')); + }, time); +}; + +CompactBlock.prototype.complete = function complete(err) { + if (this.timeout != null) { + clearTimeout(this.timeout); + this.timeout = null; + this.callback(err); + } +}; + +CompactBlock.prototype.destroy = function destroy() { if (this.timeout != null) { clearTimeout(this.timeout); this.timeout = null; diff --git a/lib/net/peer.js b/lib/net/peer.js index 26310a4c..36b7e934 100644 --- a/lib/net/peer.js +++ b/lib/net/peer.js @@ -179,10 +179,6 @@ Peer.uid = 0; Peer.prototype._init = function init() { var self = this; - this.socket.once('connect', function() { - self._onConnect(); - }); - this.socket.once('error', function(err) { self.error(err); @@ -229,11 +225,96 @@ Peer.prototype._init = function init() { }); } - if (this.connected) { - utils.nextTick(function() { - self._onConnect(); - }); + this.open(); +}; + +/** + * Create the socket and begin connecting. This method + * will use `options.createSocket` if provided. + * @param {String} host + * @param {Number} port + * @returns {net.Socket} + */ + +Peer.prototype.connect = function connect(port, host) { + var self = this; + var socket, proxy, net; + + assert(!this.socket); + + if (this.createSocket) { + socket = this.createSocket(port, host); + } else { + if (utils.isBrowser) { + proxy = require('./proxysocket'); + socket = proxy.connect(this.pool.proxyServer, port, host); + } else { + net = require('net'); + socket = net.connect(port, host); + } } + + this.logger.debug('Connecting to %s.', this.hostname); + + socket.once('connect', function() { + self.logger.info('Connected to %s.', self.hostname); + }); + + return socket; +}; + +/** + * Open and initialize the peer. + */ + +Peer.prototype.open = co(function* open() { + try { + yield this._connect(); + yield this._bip151(); + yield this._bip150(); + yield this._handshake(); + yield this._finalize(); + } catch (e) { + this.error(e); + return; + } + + // Finally we can let the pool know + // that this peer is ready to go. + this.emit('open'); +}); + +/** + * Wait for connection. + * @private + */ + +Peer.prototype._connect = function _connect() { + var self = this; + + if (this.connected) { + assert(!this.outbound); + return spawn.wait(); + } + + return new Promise(function(resolve, reject) { + self.socket.once('connect', function() { + self.ts = utils.now(); + self.connected = true; + self.emit('connect'); + + clearTimeout(self.connectTimeout); + self.connectTimeout = null; + + resolve(); + }); + + self.connectTimeout = setTimeout(function() { + self.connectTimeout = null; + reject(new Error('Connection timed out.')); + self.ignore(); + }, 10000); + }); }; /** @@ -242,96 +323,76 @@ Peer.prototype._init = function init() { * @private */ -Peer.prototype._onConnect = function _onConnect() { - var self = this; - - this.ts = utils.now(); - this.connected = true; - - this.emit('connect'); - - if (this.connectTimeout != null) { - clearTimeout(this.connectTimeout); - this.connectTimeout = null; - } - +Peer.prototype._bip151 = co(function* _bip151() { // Send encinit. Wait for handshake to complete. - if (this.bip151) { - assert(!this.bip151.completed); - this.logger.info('Attempting BIP151 handshake (%s).', this.hostname); - this.send(this.bip151.toEncinit()); - return this.bip151.wait(3000, function(err) { - if (err) - self.error(err, true); - self._onBIP151(); - }); - } + if (!this.bip151) + return; - this._onBIP151(); -}; + assert(!this.bip151.completed); + + this.logger.info('Attempting BIP151 handshake (%s).', this.hostname); + + this.send(this.bip151.toEncinit()); + + try { + yield this.bip151.wait(3000); + } catch (err) { + this.error(err, true); + } +}); /** * Handle post bip151-handshake. * @private */ -Peer.prototype._onBIP151 = function _onBIP151() { - var self = this; +Peer.prototype._bip150 = co(function* _bip150() { + if (!this.bip151) + return; - if (this.bip151) { - assert(this.bip151.completed); + assert(this.bip151.completed); - if (this.bip151.handshake) { - this.logger.info('BIP151 handshake complete (%s).', this.hostname); - this.logger.info('Connection is encrypted (%s).', this.hostname); - } - - if (this.bip150) { - assert(!this.bip150.completed); - - if (!this.bip151.handshake) - return this.error('BIP151 handshake was not completed for BIP150.'); - - this.logger.info('Attempting BIP150 handshake (%s).', this.hostname); - - if (this.bip150.outbound) { - if (!this.bip150.peerIdentity) - return this.error('No known identity for peer.'); - this.send(this.bip150.toChallenge()); - } - - return this.bip150.wait(3000, function(err) { - if (err) - return self.error(err); - self._onHandshake(); - }); - } + if (this.bip151.handshake) { + this.logger.info('BIP151 handshake complete (%s).', this.hostname); + this.logger.info('Connection is encrypted (%s).', this.hostname); } - this._onHandshake(); -}; + if (!this.bip150) + return; + + assert(!this.bip150.completed); + + if (!this.bip151.handshake) + throw new Error('BIP151 handshake was not completed for BIP150.'); + + this.logger.info('Attempting BIP150 handshake (%s).', this.hostname); + + if (this.bip150.outbound) { + if (!this.bip150.peerIdentity) + return this.error('No known identity for peer.'); + this.send(this.bip150.toChallenge()); + } + + yield this.bip150.wait(3000); + + if (!this.bip150) + return; + + assert(this.bip150.completed); + + if (this.bip150.auth) { + this.logger.info('BIP150 handshake complete (%s).', this.hostname); + this.logger.info('Peer is authed (%s): %s.', + this.hostname, this.bip150.getAddress()); + } +}); /** * Handle post handshake. * @private */ -Peer.prototype._onHandshake = function _onHandshake() { - var self = this; - - if (this.bip150) { - assert(this.bip150.completed); - if (this.bip150.auth) { - this.logger.info('BIP150 handshake complete (%s).', this.hostname); - this.logger.info('Peer is authed (%s): %s.', - this.hostname, this.bip150.getAddress()); - } - } - - this.request('verack', function(err) { - self._onAck(err); - }); - +Peer.prototype._handshake = co(function* _handshake() { // Say hello. this.sendVersion(); @@ -341,32 +402,33 @@ Peer.prototype._onHandshake = function _onHandshake() { && this.pool.server) { this.send(new packets.AddrPacket([this.pool.address])); } -}; -/** - * Handle `ack` event (called on verack). - * @private - */ - -Peer.prototype._onAck = function _onAck(err) { - var self = this; - - if (err) { - this.error(err); - return; - } + yield this.request('verack'); // Wait for _their_ version. if (!this.version) { this.logger.debug( 'Peer sent a verack without a version (%s).', this.hostname); - this.request('version', this._onAck.bind(this)); - return; + + yield this.request('version'); + + assert(this.version); } this.ack = true; + this.logger.debug('Received verack (%s).', this.hostname); +}); + +/** + * Handle `ack` event (called on verack). + * @private + */ + +Peer.prototype._finalize = co(function* _finalize() { + var self = this; + // Setup the ping interval. this.pingTimeout = setInterval(function() { self.sendPing(); @@ -407,53 +469,7 @@ Peer.prototype._onAck = function _onAck(err) { // Start syncing the chain. this.sync(); - - this.logger.debug('Received verack (%s).', this.hostname); - - // Finally we can let the pool know - // that this peer is ready to go. - this.emit('ack'); -}; - -/** - * Create the socket and begin connecting. This method - * will use `options.createSocket` if provided. - * @param {String} host - * @param {Number} port - * @returns {net.Socket} - */ - -Peer.prototype.connect = function connect(port, host) { - var self = this; - var socket, proxy, net; - - assert(!this.socket); - - if (this.createSocket) { - socket = this.createSocket(port, host); - } else { - if (utils.isBrowser) { - proxy = require('./proxysocket'); - socket = proxy.connect(this.pool.proxyServer, port, host); - } else { - net = require('net'); - socket = net.connect(port, host); - } - } - - this.logger.debug('Connecting to %s.', this.hostname); - - socket.once('connect', function() { - self.logger.info('Connected to %s.', self.hostname); - }); - - this.connectTimeout = setTimeout(function() { - self.error('Connection timed out.'); - self.ignore(); - }, 10000); - - return socket; -}; +}); /** * Test whether the peer is the loader peer. @@ -604,6 +620,7 @@ Peer.prototype.sendVersion = function sendVersion() { height: this.chain.height, relay: this.options.relay }); + this.send(packet); }; @@ -678,7 +695,7 @@ Peer.prototype.sendFeeRate = function sendFeeRate(rate) { */ Peer.prototype.destroy = function destroy() { - var i, j, keys, cmd, queue; + var i, j, keys, cmd, queue, hash; if (this.destroyed) return; @@ -715,6 +732,13 @@ Peer.prototype.destroy = function destroy() { queue[j].destroy(); } + keys = Object.keys(this.compactBlocks); + + for (i = 0; i < keys.length; i++) { + hash = keys[i]; + this.compactBlocks[hash].destroy(); + } + this.emit('close'); }; @@ -797,20 +821,21 @@ Peer.prototype.error = function error(err, keep) { * Executed on timeout or once packet is received. */ -Peer.prototype.request = function request(cmd, callback) { - var entry; +Peer.prototype.request = function request(cmd) { + var self = this; + return new Promise(function(resolve, reject) { + var entry; - if (this.destroyed) - return callback(new Error('Destroyed')); + if (self.destroyed) + return reject(new Error('Destroyed')); - entry = new RequestEntry(this, cmd, callback); + entry = new RequestEntry(self, cmd, resolve, reject); - if (!this.requestMap[cmd]) - this.requestMap[cmd] = []; + if (!self.requestMap[cmd]) + self.requestMap[cmd] = []; - this.requestMap[cmd].push(entry); - - return entry; + self.requestMap[cmd].push(entry); + }); }; /** @@ -832,7 +857,7 @@ Peer.prototype.response = function response(cmd, payload) { if (!entry) return false; - res = entry.callback(null, payload, cmd); + res = entry.resolve(payload); if (res === false) return false; @@ -1114,11 +1139,19 @@ Peer.prototype._handleGetUTXOs = co(function* _handleGetUTXOs(packet) { var unlock = yield this.locker.lock(); try { return yield this.__handleGetUTXOs(packet); + } catch (e) { + this.emit('error', e); } finally { unlock(); } }); +/** + * Handle `getheaders` packet without lock. + * @private + * @param {GetHeadersPacket} + */ + Peer.prototype.__handleGetUTXOs = co(function* _handleGetUTXOs(packet) { var i, utxos, prevout, hash, index, coin; @@ -1142,12 +1175,7 @@ Peer.prototype.__handleGetUTXOs = co(function* _handleGetUTXOs(packet) { index = prevout.index; if (this.mempool && packet.mempool) { - try { - coin = this.mempool.getCoin(hash, index); - } catch (e) { - this.emit('error', e); - return; - } + coin = this.mempool.getCoin(hash, index); if (coin) { utxos.hits.push(1); @@ -1161,12 +1189,7 @@ Peer.prototype.__handleGetUTXOs = co(function* _handleGetUTXOs(packet) { } } - try { - coin = yield this.chain.db.getCoin(hash, index); - } catch (e) { - this.emit('error', e); - return; - } + coin = yield this.chain.db.getCoin(hash, index); if (!coin) { utxos.hits.push(0); @@ -1203,12 +1226,20 @@ Peer.prototype._handleHaveWitness = function _handleHaveWitness(packet) { Peer.prototype._handleGetHeaders = co(function* _handleGetHeaders(packet) { var unlock = yield this.locker.lock(); try { - return this.__handleGetHeaders(packet); + return yield this.__handleGetHeaders(packet); + } catch (e) { + this.emit('error', e); } finally { unlock(); } }); +/** + * Handle `getheaders` packet without lock. + * @private + * @param {GetHeadersPacket} + */ + Peer.prototype.__handleGetHeaders = co(function* _handleGetHeaders(packet) { var headers = []; var hash, entry; @@ -1260,12 +1291,20 @@ Peer.prototype.__handleGetHeaders = co(function* _handleGetHeaders(packet) { Peer.prototype._handleGetBlocks = co(function* _handleGetBlocks(packet) { var unlock = yield this.locker.lock(); try { - return this.__handleGetBlocks(packet); + return yield this.__handleGetBlocks(packet); + } catch (e) { + this.emit('error', e); } finally { unlock(); } }); +/** + * Handle `getblocks` packet without lock. + * @private + * @param {GetBlocksPacket} + */ + Peer.prototype.__handleGetBlocks = co(function* _handleGetBlocks(packet) { var blocks = []; var hash; @@ -1310,7 +1349,7 @@ Peer.prototype.__handleGetBlocks = co(function* _handleGetBlocks(packet) { * @param {VersionPacket} */ -Peer.prototype._handleVersion = function _handleVersion(version) { +Peer.prototype._handleVersion = co(function* _handleVersion(version) { var self = this; if (!this.network.selfConnect) { @@ -1352,43 +1391,33 @@ Peer.prototype._handleVersion = function _handleVersion(version) { } if (this.options.witness) { - if (!version.hasWitness()) { + this.haveWitness = version.hasWitness(); + + if (!this.haveWitness) { if (!this.network.oldWitness) { this.error('Peer does not support segregated witness.'); this.ignore(); return; } - return this.request('havewitness', function(err) { - if (err) { - self.error('Peer does not support segregated witness.'); - self.ignore(); - return; - } - self._finishVersion(version); - }); + + try { + yield this.request('havewitness'); + } catch (err) { + this.error('Peer does not support segregated witness.'); + this.ignore(); + return; + } + + this.haveWitness = true; } } - this._finishVersion(version); -}; - -/** - * Finish handling `version` packet. - * @private - * @param {VersionPacket} - */ - -Peer.prototype._finishVersion = function _finishVersion(version) { - if (version.hasWitness()) - this.haveWitness = true; - - if (!version.relay) - this.relay = false; + this.relay = version.relay; + this.version = version; this.send(new packets.VerackPacket()); - this.version = version; this.fire('version', version); -}; +}); /** * Handle `verack` packet. @@ -1407,7 +1436,6 @@ Peer.prototype._handleVerack = function _handleVerack(packet) { */ Peer.prototype._handleMempool = function _handleMempool(packet) { - var self = this; var items = []; var i, hashes; @@ -1425,9 +1453,9 @@ Peer.prototype._handleMempool = function _handleMempool(packet) { for (i = 0; i < hashes.length; i++) items.push(new InvItem(constants.inv.TX, hashes[i])); - self.logger.debug('Sending mempool snapshot (%s).', self.hostname); + this.logger.debug('Sending mempool snapshot (%s).', this.hostname); - self.sendInv(items); + this.sendInv(items); }; /** @@ -1490,11 +1518,19 @@ Peer.prototype._handleGetData = co(function* _handleGetData(packet) { var unlock = yield this.locker.lock(); try { return yield this.__handleGetData(packet); + } catch (e) { + this.emit('error', e); } finally { unlock(); } }); +/** + * Handle `getdata` packet without lock. + * @private + * @param {GetDataPacket} + */ + Peer.prototype._handleGetData = co(function* _handleGetData(packet) { var notFound = []; var items = packet.items; @@ -2032,8 +2068,7 @@ Peer.prototype._handleSendCmpct = function _handleSendCmpct(packet) { * @param {CmpctBlockPacket} */ -Peer.prototype._handleCmpctBlock = function _handleCmpctBlock(packet) { - var self = this; +Peer.prototype._handleCmpctBlock = co(function* _handleCmpctBlock(packet) { var block = packet.block; var hash = block.hash('hex'); var result; @@ -2075,13 +2110,16 @@ Peer.prototype._handleCmpctBlock = function _handleCmpctBlock(packet) { 'Received semi-full compact block %s (%s).', block.rhash, this.hostname); - block.startTimeout(10000, function() { - self.logger.debug( + try { + yield block.wait(10000); + } catch (e) { + this.logger.debug( 'Compact block timed out: %s (%s).', - block.rhash, self.hostname); - delete self.compactBlocks[hash]; - }); -}; + block.rhash, this.hostname); + + delete this.compactBlocks[hash]; + } +}); /** * Handle `getblocktxn` packet. @@ -2089,18 +2127,9 @@ Peer.prototype._handleCmpctBlock = function _handleCmpctBlock(packet) { * @param {GetBlockTxnPacket} */ -Peer.prototype._handleGetBlockTxn = function _handleGetBlockTxn(packet) { - var self = this; +Peer.prototype._handleGetBlockTxn = co(function* _handleGetBlockTxn(packet) { var req = packet.request; - var res, item; - - function done(err) { - if (err) { - self.emit('error', err); - return; - } - self.fire('blocktxn', req); - } + var res, item, block; if (this.chain.db.options.spv) return done(); @@ -2113,32 +2142,28 @@ Peer.prototype._handleGetBlockTxn = function _handleGetBlockTxn(packet) { item = new InvItem(constants.inv.BLOCK, req.hash); - this._getItem(item, function(err, block) { - if (err) - return done(err); + block = yield this._getItem(item); - if (!block) { - self.logger.debug( - 'Peer sent getblocktxn for non-existent block (%s).', - self.hostname); - self.setMisbehavior(100); - return done(); - } + if (!block) { + this.logger.debug( + 'Peer sent getblocktxn for non-existent block (%s).', + this.hostname); + this.setMisbehavior(100); + return; + } - if (block.height < self.chain.tip.height - 15) { - self.logger.debug( - 'Peer sent a getblocktxn for a block > 15 deep (%s)', - self.hostname); - return done(); - } + if (block.height < this.chain.tip.height - 15) { + this.logger.debug( + 'Peer sent a getblocktxn for a block > 15 deep (%s)', + this.hostname); + return; + } - res = bcoin.bip152.TXResponse.fromBlock(block, req); + res = bcoin.bip152.TXResponse.fromBlock(block, req); - self.send(new packets.BlockTxnPacket(res, false)); - - done(); - }); -}; + this.send(new packets.BlockTxnPacket(res, false)); + this.fire('blocktxn', req); +}); /** * Handle `blocktxn` packet. @@ -2155,9 +2180,8 @@ Peer.prototype._handleBlockTxn = function _handleBlockTxn(packet) { return; } - this.fire('getblocktxn', res); + block.complete(); - block.stopTimeout(); delete this.compactBlocks[res.hash]; if (!block.fillMissing(res)) { @@ -2171,6 +2195,7 @@ Peer.prototype._handleBlockTxn = function _handleBlockTxn(packet) { block.rhash, this.hostname); this.fire('block', block.toBlock()); + this.fire('getblocktxn', res); }; /** @@ -2461,10 +2486,11 @@ Peer.prototype.inspect = function inspect() { * @constructor */ -function RequestEntry(peer, cmd, callback) { +function RequestEntry(peer, cmd, resolve, reject) { this.peer = peer; this.cmd = cmd; - this.callback = callback; + this.resolve = resolve; + this.reject = reject; this.id = peer.uid++; this.onTimeout = this._onTimeout.bind(this); this.timeout = setTimeout(this.onTimeout, this.peer.requestTimeout); @@ -2479,7 +2505,7 @@ RequestEntry.prototype._onTimeout = function _onTimeout() { if (utils.binaryRemove(queue, this, compare)) { if (queue.length === 0) delete this.peer.requestMap[this.cmd]; - this.callback(new Error('Timed out: ' + this.cmd)); + this.reject(new Error('Timed out: ' + this.cmd)); } }; diff --git a/lib/net/pool.js b/lib/net/pool.js index 87b2cfdc..42675a69 100644 --- a/lib/net/pool.js +++ b/lib/net/pool.js @@ -1006,7 +1006,7 @@ Pool.prototype.createPeer = function createPeer(addr, socket) { var self = this; var peer = new bcoin.peer(this, addr, socket); - peer.once('ack', function() { + peer.once('open', function() { if (!peer.outbound) return; diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index 6009326d..9d330cb3 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -384,7 +384,7 @@ TXDB.prototype.getInfo = function getInfo(tx) { * @param {Function} callback - Returns [Error, Buffer]. */ -TXDB.prototype._addOrphan = co(function* _addOrphan(prevout, spender) { +TXDB.prototype.addOrphan = co(function* addOrphan(prevout, spender) { var p = new BufferWriter(); var key = layout.o(prevout.hash, prevout.index); var data = yield this.get(key); @@ -405,7 +405,7 @@ TXDB.prototype._addOrphan = co(function* _addOrphan(prevout, spender) { * @param {Function} callback - Returns [Error, {@link Orphan}]. */ -TXDB.prototype._getOrphans = co(function* _getOrphans(hash, index) { +TXDB.prototype.getOrphans = co(function* getOrphans(hash, index) { var items = []; var i, data, orphans, orphan, tx, p; @@ -438,7 +438,7 @@ TXDB.prototype._getOrphans = co(function* _getOrphans(hash, index) { * @param {Function} callback - Returns [Error]. */ -TXDB.prototype._verify = co(function* _verify(tx, info) { +TXDB.prototype.verify = co(function* verify(tx, info) { var i, input, prevout, address, coin, spent, rtx, rinfo, result; for (i = 0; i < tx.inputs.length; i++) { @@ -494,7 +494,7 @@ TXDB.prototype._verify = co(function* _verify(tx, info) { this.logger.warning('Removing conflicting tx: %s.', utils.revHex(spent.hash)); - result = yield this._removeConflict(spent.hash, tx); + result = yield this.removeConflict(spent.hash, tx); // Spender was not removed, the current // transaction is not elligible to be added. @@ -519,11 +519,11 @@ TXDB.prototype._verify = co(function* _verify(tx, info) { * @param {Function} callback */ -TXDB.prototype._resolveOrphans = co(function* _resolveOrphans(tx, index) { +TXDB.prototype.resolveOrphans = co(function* resolveOrphans(tx, index) { var hash = tx.hash('hex'); var i, orphans, coin, item, input, orphan; - orphans = yield this._getOrphans(hash, index); + orphans = yield this.getOrphans(hash, index); if (!orphans) return false; @@ -554,7 +554,7 @@ TXDB.prototype._resolveOrphans = co(function* _resolveOrphans(tx, index) { return true; } - yield this._lazyRemove(orphan); + yield this.lazyRemove(orphan); } // Just going to be added again outside. @@ -564,9 +564,7 @@ TXDB.prototype._resolveOrphans = co(function* _resolveOrphans(tx, index) { }); /** - * Add transaction, runs _confirm (separate batch) and - * verify (separate batch for double spenders). - * @private + * Add transaction, runs `confirm()` and `verify()`. * @param {TX} tx * @param {PathInfo} info * @param {Function} callback @@ -575,13 +573,21 @@ TXDB.prototype._resolveOrphans = co(function* _resolveOrphans(tx, index) { TXDB.prototype.add = co(function* add(tx, info) { var unlock = yield this.locker.lock(); try { - return yield this.addl(tx, info); + return yield this._add(tx, info); } finally { unlock(); } }); -TXDB.prototype.addl = co(function* add(tx, info) { +/** + * Add transaction without a lock. + * @private + * @param {TX} tx + * @param {PathInfo} info + * @param {Function} callback + */ + +TXDB.prototype._add = co(function* add(tx, info) { var hash, path, account; var i, result, input, output, coin; var prevout, key, address, spender, orphans; @@ -589,14 +595,14 @@ TXDB.prototype.addl = co(function* add(tx, info) { assert(!tx.mutable, 'Cannot add mutable TX to wallet.'); // Attempt to confirm tx before adding it. - result = yield this._confirm(tx, info); + result = yield this.confirm(tx, info); // Ignore if we already have this tx. if (result) return true; // Verify and get coins. - result = yield this._verify(tx, info); + result = yield this.verify(tx, info); if (!result) return false; @@ -647,7 +653,7 @@ TXDB.prototype.addl = co(function* add(tx, info) { // Add orphan, if no parent transaction is yet known if (!input.coin) { try { - yield this._addOrphan(prevout, spender); + yield this.addOrphan(prevout, spender); } catch (e) { this.drop(); throw e; @@ -676,7 +682,7 @@ TXDB.prototype.addl = co(function* add(tx, info) { continue; try { - orphans = yield this._resolveOrphans(tx, i); + orphans = yield this.resolveOrphans(tx, i); } catch (e) { this.drop(); throw e; @@ -720,7 +726,7 @@ TXDB.prototype.addl = co(function* add(tx, info) { * @param {Function} callback - Returns [Error, Boolean]. */ -TXDB.prototype._removeConflict = co(function* _removeConflict(hash, ref) { +TXDB.prototype.removeConflict = co(function* removeConflict(hash, ref) { var tx = yield this.getTX(hash); var info; @@ -749,7 +755,7 @@ TXDB.prototype._removeConflict = co(function* _removeConflict(hash, ref) { return; } - info = yield this._removeRecursive(tx); + info = yield this.removeRecursive(tx); return [tx, info]; }); @@ -762,7 +768,7 @@ TXDB.prototype._removeConflict = co(function* _removeConflict(hash, ref) { * @param {Function} callback - Returns [Error, Boolean]. */ -TXDB.prototype._removeRecursive = co(function* _removeRecursive(tx) { +TXDB.prototype.removeRecursive = co(function* removeRecursive(tx) { var hash = tx.hash('hex'); var i, spent, stx, info; @@ -777,14 +783,14 @@ TXDB.prototype._removeRecursive = co(function* _removeRecursive(tx) { if (!stx) throw new Error('Could not find spender.'); - yield this._removeRecursive(stx); + yield this.removeRecursive(stx); } this.start(); // Remove the spender. try { - info = yield this._lazyRemove(tx); + info = yield this.lazyRemove(tx); } catch (e) { this.drop(); throw e; @@ -848,7 +854,7 @@ TXDB.prototype.isSpent = co(function* isSpent(hash, index) { * transaction was confirmed, or should be ignored. */ -TXDB.prototype._confirm = co(function* _confirm(tx, info) { +TXDB.prototype.confirm = co(function* confirm(tx, info) { var hash = tx.hash('hex'); var i, account, existing, output, coin; var address, key; @@ -945,20 +951,27 @@ TXDB.prototype._confirm = co(function* _confirm(tx, info) { TXDB.prototype.remove = co(function* remove(hash) { var unlock = yield this.locker.lock(); try { - return yield this.removel(hash); + return yield this._remove(hash); } finally { unlock(); } }); -TXDB.prototype.removel = co(function* remove(hash) { +/** + * Remove a transaction without a lock. + * @private + * @param {Hash} hash + * @param {Function} callback - Returns [Error]. + */ + +TXDB.prototype._remove = co(function* remove(hash) { var tx = yield this.getTX(hash); var info; if (!tx) return; - info = yield this._removeRecursive(tx); + info = yield this.removeRecursive(tx); if (!info) return; @@ -974,12 +987,12 @@ TXDB.prototype.removel = co(function* remove(hash) { * @param {Function} callback - Returns [Error]. */ -TXDB.prototype._lazyRemove = co(function* lazyRemove(tx) { +TXDB.prototype.lazyRemove = co(function* lazyRemove(tx) { var info = yield this.getInfo(tx); if (!info) return; - return yield this._remove(tx, info); + return yield this.__remove(tx, info); }); /** @@ -990,7 +1003,7 @@ TXDB.prototype._lazyRemove = co(function* lazyRemove(tx) { * @param {Function} callback - Returns [Error]. */ -TXDB.prototype._remove = co(function* remove(tx, info) { +TXDB.prototype.__remove = co(function* remove(tx, info) { var hash = tx.hash('hex'); var i, path, account, key, prevout; var address, input, output, coin; @@ -1080,13 +1093,20 @@ TXDB.prototype._remove = co(function* remove(tx, info) { TXDB.prototype.unconfirm = co(function* unconfirm(hash) { var unlock = yield this.locker.lock(); try { - return yield this.unconfirml(hash); + return yield this._unconfirm(hash); } finally { unlock(); } }); -TXDB.prototype.unconfirml = co(function* unconfirm(hash) { +/** + * Unconfirm a transaction without a lock. + * @private + * @param {Hash} hash + * @param {Function} callback + */ + +TXDB.prototype._unconfirm = co(function* unconfirm(hash) { var tx = yield this.getTX(hash); var info, result; @@ -1101,7 +1121,7 @@ TXDB.prototype.unconfirml = co(function* unconfirm(hash) { this.start(); try { - result = yield this._unconfirm(tx, info); + result = yield this.__unconfirm(tx, info); } catch (e) { this.drop(); throw e; @@ -1119,7 +1139,7 @@ TXDB.prototype.unconfirml = co(function* unconfirm(hash) { * @param {Function} callback */ -TXDB.prototype._unconfirm = co(function* unconfirm(tx, info) { +TXDB.prototype.__unconfirm = co(function* unconfirm(tx, info) { var hash = tx.hash('hex'); var height = tx.height; var i, account, output, key, coin; @@ -1613,8 +1633,10 @@ TXDB.prototype.getAccountCoins = co(function* getCoins(account) { for (i = 0; i < hashes.length; i++) { key = hashes[i]; coin = yield this.getCoin(key[0], key[1]); + if (!coin) continue; + coins.push(coin); } @@ -1921,6 +1943,7 @@ TXDB.prototype.getAccountBalance = co(function* getBalance(account) { }); /** + * Zap pending transactions older than `age`. * @param {Number?} account * @param {Number} age - Age delta (delete transactions older than `now - age`). * @param {Function} callback @@ -1935,6 +1958,14 @@ TXDB.prototype.zap = co(function* zap(account, age) { } }); +/** + * Zap pending transactions without a lock. + * @private + * @param {Number?} account + * @param {Number} age + * @param {Function} callback + */ + TXDB.prototype._zap = co(function* zap(account, age) { var i, txs, tx, hash; @@ -1953,7 +1984,7 @@ TXDB.prototype._zap = co(function* zap(account, age) { if (tx.ts !== 0) continue; - yield this.removel(hash); + yield this._remove(hash); } }); diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js index 76b723c6..468e8c02 100644 --- a/lib/wallet/wallet.js +++ b/lib/wallet/wallet.js @@ -236,6 +236,13 @@ Wallet.prototype.addKey = co(function* addKey(account, key) { } }); +/** + * Add a public account key to the wallet without a lock. + * @private + * @param {HDPublicKey} key + * @param {Function} callback + */ + Wallet.prototype._addKey = co(function* addKey(account, key) { var result; @@ -255,7 +262,7 @@ Wallet.prototype._addKey = co(function* addKey(account, key) { this.start(); try { - result = yield account.addKey(key, true); + result = yield account.addKey(key); } catch (e) { this.drop(); throw e; @@ -268,7 +275,6 @@ Wallet.prototype._addKey = co(function* addKey(account, key) { /** * Remove a public account key from the wallet (multisig). - * Remove the key from the wallet database. * @param {HDPublicKey} key * @param {Function} callback */ @@ -282,6 +288,13 @@ Wallet.prototype.removeKey = co(function* removeKey(account, key) { } }); +/** + * Remove a public account key from the wallet (multisig). + * @private + * @param {HDPublicKey} key + * @param {Function} callback + */ + Wallet.prototype._removeKey = co(function* removeKey(account, key) { var result; @@ -301,7 +314,7 @@ Wallet.prototype._removeKey = co(function* removeKey(account, key) { this.start(); try { - result = yield account.removeKey(key, true); + result = yield account.removeKey(key); } catch (e) { this.drop(); throw e; @@ -328,6 +341,14 @@ Wallet.prototype.setPassphrase = co(function* setPassphrase(old, new_) { } }); +/** + * Change or set master key's passphrase without a lock. + * @private + * @param {(String|Buffer)?} old + * @param {String|Buffer} new_ + * @param {Function} callback + */ + Wallet.prototype._setPassphrase = co(function* setPassphrase(old, new_) { if (new_ == null) { new_ = old; @@ -361,6 +382,13 @@ Wallet.prototype.retoken = co(function* retoken(passphrase) { } }); +/** + * Generate a new token without a lock. + * @private + * @param {(String|Buffer)?} passphrase + * @param {Function} callback + */ + Wallet.prototype._retoken = co(function* retoken(passphrase) { var master = yield this.unlock(passphrase); @@ -463,6 +491,12 @@ Wallet.prototype.createAccount = co(function* createAccount(options) { } }); +/** + * Create an account without a lock. + * @param {Object} options - See {@link Account} options. + * @param {Function} callback - Returns [Error, {@link Account}]. + */ + Wallet.prototype._createAccount = co(function* createAccount(options) { var passphrase = options.passphrase; var timeout = options.timeout; @@ -618,6 +652,13 @@ Wallet.prototype.createAddress = co(function* createAddress(account, change) { } }); +/** + * Create a new address (increments depth) without a lock. + * @param {(Number|String)?} account + * @param {Boolean} change + * @param {Function} callback - Returns [Error, {@link KeyRing}]. + */ + Wallet.prototype._createAddress = co(function* createAddress(account, change) { var result; @@ -763,6 +804,15 @@ Wallet.prototype.importKey = co(function* importKey(account, ring, passphrase) { } }); +/** + * Import a keyring (will not exist on derivation chain) without a lock. + * @private + * @param {(String|Number)?} account + * @param {KeyRing} ring + * @param {(String|Buffer)?} passphrase + * @param {Function} callback + */ + Wallet.prototype._importKey = co(function* importKey(account, ring, passphrase) { var exists, raw, path; @@ -847,7 +897,14 @@ Wallet.prototype.fund = co(function* fund(tx, options, force) { } }); -Wallet.prototype._fund = co(function* fund(tx, options, force) { +/** + * Fill a transaction with inputs without a lock. + * @private + * @see MTX#selectCoins + * @see MTX#fill + */ + +Wallet.prototype._fund = co(function* fund(tx, options) { var rate, account, coins; if (!options) @@ -968,6 +1025,14 @@ Wallet.prototype.send = co(function* send(options) { } }); +/** + * Build and send a transaction without a lock. + * @private + * @param {Object} options - See {@link Wallet#fund options}. + * @param {Object[]} options.outputs - See {@link MTX#addOutput}. + * @param {Function} callback - Returns [Error, {@link TX}]. + */ + Wallet.prototype._send = co(function* send(options) { var tx = yield this.createTX(options, true); @@ -1144,6 +1209,13 @@ Wallet.prototype.syncOutputDepth = co(function* syncOutputDepth(info) { } }); +/** + * Sync address depths without a lock. + * @private + * @param {PathInfo} info + * @param {Function} callback - Returns [Error, Boolean] + */ + Wallet.prototype._syncOutputDepth = co(function* syncOutputDepth(info) { var receive = []; var accounts = {}; @@ -1300,7 +1372,7 @@ Wallet.prototype.template = co(function* template(tx) { */ Wallet.prototype.sign = co(function* sign(tx, options) { - var passphrase, timeout, master, rings; + var master, rings; if (!options) options = {}; @@ -1308,10 +1380,8 @@ Wallet.prototype.sign = co(function* sign(tx, options) { if (typeof options === 'string' || Buffer.isBuffer(options)) options = { passphrase: options }; - passphrase = options.passphrase; - timeout = options.timeout; + master = yield this.unlock(options.passphrase, options.timeout); - master = yield this.unlock(passphrase, timeout); rings = yield this.deriveInputs(tx); return yield this.signAsync(rings, tx); @@ -1991,6 +2061,14 @@ MasterKey.prototype.unlock = co(function* _unlock(passphrase, timeout) { } }); +/** + * Decrypt the key without a lock. + * @private + * @param {Buffer|String} passphrase - Zero this yourself. + * @param {Number} [timeout=60000] timeout in ms. + * @param {Function} callback - Returns [Error, {@link HDPrivateKey}]. + */ + MasterKey.prototype._unlock = co(function* _unlock(passphrase, timeout) { var data, key; @@ -2046,6 +2124,13 @@ MasterKey.prototype.stop = function stop() { } }; +/** + * Encrypt data with in-memory aes key. + * @param {Buffer} data + * @param {Buffer} iv + * @returns {Buffer} + */ + MasterKey.prototype.encipher = function encipher(data, iv) { if (!this.aesKey) return; @@ -2056,6 +2141,13 @@ MasterKey.prototype.encipher = function encipher(data, iv) { return crypto.encipher(data, this.aesKey, iv.slice(0, 16)); }; +/** + * Decrypt data with in-memory aes key. + * @param {Buffer} data + * @param {Buffer} iv + * @returns {Buffer} + */ + MasterKey.prototype.decipher = function decipher(data, iv) { if (!this.aesKey) return; @@ -2107,6 +2199,13 @@ MasterKey.prototype.decrypt = co(function* decrypt(passphrase) { } }); +/** + * Decrypt the key permanently without a lock. + * @private + * @param {Buffer|String} passphrase - Zero this yourself. + * @param {Function} callback + */ + MasterKey.prototype._decrypt = co(function* decrypt(passphrase) { var data; @@ -2143,6 +2242,13 @@ MasterKey.prototype.encrypt = co(function* encrypt(passphrase) { } }); +/** + * Encrypt the key permanently without a lock. + * @private + * @param {Buffer|String} passphrase - Zero this yourself. + * @param {Function} callback + */ + MasterKey.prototype._encrypt = co(function* encrypt(passphrase) { var data, iv; diff --git a/lib/wallet/walletdb.js b/lib/wallet/walletdb.js index e4c1f914..4690e680 100644 --- a/lib/wallet/walletdb.js +++ b/lib/wallet/walletdb.js @@ -471,6 +471,13 @@ WalletDB.prototype.get = co(function* get(wid) { } }); +/** + * Get a wallet from the database without a lock. + * @private + * @param {WalletID} wid + * @param {Function} callback - Returns [Error, {@link Wallet}]. + */ + WalletDB.prototype._get = co(function* get(wid) { var data = yield this.db.get(layout.w(wid)); var wallet; @@ -490,7 +497,6 @@ WalletDB.prototype._get = co(function* get(wid) { /** * Save a wallet to the database. * @param {Wallet} wallet - * @param {Function} callback */ WalletDB.prototype.save = function save(wallet) { @@ -548,6 +554,13 @@ WalletDB.prototype.create = co(function* create(options) { } }); +/** + * Create a new wallet, save to database without a lock. + * @private + * @param {Object} options - See {@link Wallet}. + * @param {Function} callback - Returns [Error, {@link Wallet}]. + */ + WalletDB.prototype._create = co(function* create(options) { var exists = yield this.has(options.id); var wallet; @@ -611,11 +624,12 @@ WalletDB.prototype.getAccount = co(function* getAccount(wid, name) { return; yield account.open(); + return account; }); /** - * Get an account from the database. Do not setup watcher. + * Get an account from the database by wid. * @private * @param {WalletID} wid * @param {Number} index - Account index. @@ -942,6 +956,7 @@ WalletDB.prototype.getWallets = function getWallets() { /** * Rescan the blockchain. * @param {ChainDB} chaindb + * @param {Number} height * @param {Function} callback */ @@ -954,6 +969,14 @@ WalletDB.prototype.rescan = co(function* rescan(chaindb, height) { } }); +/** + * Rescan the blockchain without a lock. + * @private + * @param {ChainDB} chaindb + * @param {Number} height + * @param {Function} callback + */ + WalletDB.prototype._rescan = co(function* rescan(chaindb, height) { var self = this; var hashes; @@ -1241,6 +1264,13 @@ WalletDB.prototype.addBlock = co(function* addBlock(entry, txs) { } }); +/** + * Add a block's transactions without a lock. + * @private + * @param {ChainEntry} entry + * @param {Function} callback + */ + WalletDB.prototype._addBlock = co(function* addBlock(entry, txs) { var i, block, matches, hash, tx, wallets; @@ -1299,6 +1329,13 @@ WalletDB.prototype.removeBlock = co(function* removeBlock(entry) { } }); +/** + * Unconfirm a block's transactions. + * @private + * @param {ChainEntry} entry + * @param {Function} callback + */ + WalletDB.prototype._removeBlock = co(function* removeBlock(entry) { var block = WalletBlock.fromEntry(entry); var i, j, data, hash, wallets, wid, wallet; @@ -1361,6 +1398,13 @@ WalletDB.prototype.addTX = co(function* addTX(tx, force) { } }); +/** + * Add a transaction to the database without a lock. + * @private + * @param {TX} tx + * @param {Function} callback - Returns [Error]. + */ + WalletDB.prototype._addTX = co(function* addTX(tx, force) { var i, wallets, info, wallet;