diff --git a/lib/chain/chain.js b/lib/chain/chain.js index 9fdcfe36..0c96b072 100644 --- a/lib/chain/chain.js +++ b/lib/chain/chain.js @@ -235,16 +235,6 @@ Chain.prototype._close = function close() { return this.db.close(); }; -/** - * Invoke mutex lock. - * @private - * @returns {Function} unlock - */ - -Chain.prototype._lock = function _lock(block, force) { - return this.locker.lock(block, force); -}; - /** * Perform all necessary contextual verification on a block. * @private @@ -929,23 +919,23 @@ Chain.prototype.setBestChain = co(function* setBestChain(entry, block, prev) { * @param {Function} callback */ -Chain.prototype.reset = co(function* reset(height, force) { - var unlock = yield this._lock(null, force); - var result; - +Chain.prototype.reset = co(function* reset(height) { + var unlock = yield this.locker.lock(); try { - result = yield this.db.reset(height); - } catch (e) { + return yield this._reset(height); + } finally { unlock(); - throw e; } +}); + +Chain.prototype._reset = co(function* reset(height) { + var result = yield this.db.reset(height); // Reset the orphan map completely. There may // have been some orphans on a forked chain we // no longer need. this.purgeOrphans(); - unlock(); return result; }); @@ -958,19 +948,23 @@ Chain.prototype.reset = co(function* reset(height, force) { */ Chain.prototype.resetTime = co(function* resetTime(ts) { - var unlock = yield this._lock(); - var entry = yield this.byTime(ts); - - if (!entry) - return unlock(); - + var unlock = yield this.locker.lock(); try { - yield this.reset(entry.height, true); + return yield this._resetTime(ts); } finally { unlock(); } }); +Chain.prototype._resetTime = co(function* resetTime(ts) { + var entry = yield this.byTime(ts); + + if (!entry) + return; + + yield this._reset(entry.height); +}); + /** * Wait for the chain to drain (finish processing * all of the blocks in its queue). @@ -999,14 +993,23 @@ Chain.prototype.isBusy = function isBusy() { */ Chain.prototype.add = co(function* add(block) { - var ret, unlock, initial, hash, prevBlock; + var unlock = yield this.locker.lock(block); + this.currentBlock = block.hash('hex'); + try { + return yield this._add(block); + } finally { + this.currentBlock = null; + unlock(); + } +}); + +Chain.prototype._add = co(function* add(block) { + var ret, initial, hash, prevBlock; var height, checkpoint, orphan, entry; var existing, prev; assert(this.loaded); - unlock = yield this._lock(block); - ret = new VerifyResult(); initial = true; @@ -1014,23 +1017,18 @@ Chain.prototype.add = co(function* add(block) { hash = block.hash('hex'); prevBlock = block.prevBlock; - this.currentBlock = hash; this._mark(); // Do not revalidate known invalid blocks. if (this.invalid[hash] || this.invalid[prevBlock]) { this.emit('invalid', block, block.getCoinbaseHeight()); this.invalid[hash] = true; - this.currentBlock = null; - unlock(); throw new VerifyError(block, 'duplicate', 'duplicate', 100); } // Do we already have this block? if (this.hasPending(hash)) { this.emit('exists', block, block.getCoinbaseHeight()); - this.currentBlock = null; - unlock(); throw new VerifyError(block, 'duplicate', 'duplicate', 0); } @@ -1047,8 +1045,6 @@ Chain.prototype.add = co(function* add(block) { this.emit('orphan', block, block.getCoinbaseHeight()); - this.currentBlock = null; - unlock(); throw new VerifyError(block, 'invalid', 'bad-prevblk', 0); } @@ -1063,8 +1059,6 @@ Chain.prototype.add = co(function* add(block) { if (initial && !block.verify(ret)) { this.invalid[hash] = true; this.emit('invalid', block, block.getCoinbaseHeight()); - this.currentBlock = null; - unlock(); throw new VerifyError(block, 'invalid', ret.reason, ret.score); } @@ -1073,8 +1067,6 @@ Chain.prototype.add = co(function* add(block) { // Do we already have this block? if (existing) { this.emit('exists', block, block.getCoinbaseHeight()); - this.currentBlock = null; - unlock(); throw new VerifyError(block, 'duplicate', 'duplicate', 0); } @@ -1107,8 +1099,6 @@ Chain.prototype.add = co(function* add(block) { this.emit('orphan', block, block.getCoinbaseHeight()); - this.currentBlock = null; - unlock(); throw new VerifyError(block, 'invalid', 'bad-prevblk', 0); } @@ -1122,8 +1112,6 @@ Chain.prototype.add = co(function* add(block) { this.emit('fork', block, height, checkpoint); - this.currentBlock = null; - unlock(); throw new VerifyError(block, 'checkpoint', 'checkpoint mismatch', @@ -1148,8 +1136,6 @@ Chain.prototype.add = co(function* add(block) { block = block.toBlock(); } catch (e) { this.logger.error(e); - this.currentBlock = null; - unlock(); throw new VerifyError(block, 'malformed', 'error parsing message', @@ -1170,13 +1156,7 @@ Chain.prototype.add = co(function* add(block) { // our tip's. Add the block but do _not_ // connect the inputs. if (entry.chainwork.cmp(this.tip.chainwork) <= 0) { - try { - yield this.db.save(entry, block, null, false); - } catch (e) { - this.currentBlock = null; - unlock(); - throw e; - } + yield this.db.save(entry, block, null, false); this.emit('competitor', block, entry); @@ -1184,13 +1164,7 @@ Chain.prototype.add = co(function* add(block) { this.emit('competitor resolved', block, entry); } else { // Attempt to add block to the chain index. - try { - yield this.setBestChain(entry, block, prev); - } catch (e) { - this.currentBlock = null; - unlock(); - throw e; - } + yield this.setBestChain(entry, block, prev); // Emit our block (and potentially resolved // orphan) only if it is on the main chain. @@ -1228,10 +1202,6 @@ Chain.prototype.add = co(function* add(block) { this.synced = true; this.emit('full'); } - - this.currentBlock = null; - - unlock(); }); /** @@ -1522,7 +1492,15 @@ Chain.prototype.getProgress = function getProgress() { */ Chain.prototype.getLocator = co(function* getLocator(start) { - var unlock = yield this._lock(); + var unlock = yield this.locker.lock(); + try { + return yield this._getLocator(start); + } finally { + unlock(); + } +}); + +Chain.prototype._getLocator = co(function* getLocator(start) { var hashes = []; var step = 1; var height, entry, main, hash; @@ -1578,7 +1556,6 @@ Chain.prototype.getLocator = co(function* getLocator(start) { hash = entry.hash; } - unlock(); return hashes; }); diff --git a/lib/mempool/mempool.js b/lib/mempool/mempool.js index f98825b1..47a953be 100644 --- a/lib/mempool/mempool.js +++ b/lib/mempool/mempool.js @@ -85,6 +85,7 @@ function Mempool(options) { this.orphans = {}; this.tx = {}; this.spents = {}; + this.currentTX = null; this.coinIndex = new AddressIndex(this); this.txIndex = new AddressIndex(this); @@ -136,16 +137,6 @@ Mempool.prototype._close = function close() { return Promise.resolve(null); }; -/** - * Invoke mutex lock. - * @private - * @returns {Function} unlock - */ - -Mempool.prototype._lock = function _lock(tx, force) { - return this.locker.lock(tx, force); -}; - /** * Notify the mempool that a new block has come * in (removes all transactions contained in the @@ -155,7 +146,15 @@ Mempool.prototype._lock = function _lock(tx, force) { */ Mempool.prototype.addBlock = co(function* addBlock(block) { - var unlock = yield this._lock(); + var unlock = yield this.locker.lock(); + try { + return yield this._addBlock(block); + } finally { + unlock(); + } +}); + +Mempool.prototype._addBlock = co(function* addBlock(block) { var entries = []; var i, entry, tx, hash; @@ -190,7 +189,6 @@ Mempool.prototype.addBlock = co(function* addBlock(block) { this.rejects.reset(); yield spawn.wait(); - unlock(); }); /** @@ -202,6 +200,14 @@ Mempool.prototype.addBlock = co(function* addBlock(block) { Mempool.prototype.removeBlock = co(function* removeBlock(block) { var unlock = yield this.lock(); + try { + return yield this._removeBlock(block); + } finally { + unlock(); + } +}); + +Mempool.prototype._removeBlock = co(function* removeBlock(block) { var i, entry, tx, hash; for (i = 0; i < block.txs.length; i++) { @@ -216,19 +222,12 @@ Mempool.prototype.removeBlock = co(function* removeBlock(block) { entry = MempoolEntry.fromTX(tx, block.height); - try { - yield this.addUnchecked(entry, true); - } catch (e) { - unlock(); - throw e; - } + yield this._addUnchecked(entry); this.emit('unconfirmed', tx, block); } this.rejects.reset(); - - unlock(); }); /** @@ -503,6 +502,9 @@ Mempool.prototype.has = function has(hash) { if (this.hasOrphan(hash)) return true; + if (hash === this.currentTX) + return true; + return this.hasTX(hash); }; @@ -526,9 +528,11 @@ Mempool.prototype.hasReject = function hasReject(hash) { */ Mempool.prototype.addTX = co(function* addTX(tx) { - var unlock = yield this._lock(tx); + var unlock = yield this.locker.lock(tx); var missing; + this.currentTX = tx.hash('hex'); + try { missing = yield this._addTX(tx); } catch (err) { @@ -536,10 +540,12 @@ Mempool.prototype.addTX = co(function* addTX(tx) { if (!tx.hasWitness() && !err.malleated) this.rejects.add(tx.hash()); } + this.currentTX = null; unlock(); throw err; } + this.currentTX = null; unlock(); return missing; }); @@ -651,7 +657,7 @@ Mempool.prototype._addTX = co(function* _addTX(tx) { entry = MempoolEntry.fromTX(tx, this.chain.height); yield this.verify(entry); - yield this.addUnchecked(entry, true); + yield this._addUnchecked(entry); if (this.limitMempoolSize(hash)) { throw new VerifyError(tx, @@ -671,8 +677,16 @@ Mempool.prototype._addTX = co(function* _addTX(tx) { * @param {Function} callback - Returns [{@link VerifyError}]. */ -Mempool.prototype.addUnchecked = co(function* addUnchecked(entry, force) { - var unlock = yield this._lock(null, force); +Mempool.prototype.addUnchecked = co(function* addUnchecked(entry) { + var unlock = yield this.locker.lock(); + try { + return yield this._addUnchecked(entry); + } finally { + unlock(); + } +}); + +Mempool.prototype._addUnchecked = co(function* addUnchecked(entry) { var i, resolved, tx, orphan; this.trackEntry(entry); @@ -709,7 +723,7 @@ Mempool.prototype.addUnchecked = co(function* addUnchecked(entry, force) { } try { - yield this.addUnchecked(orphan, true); + yield this._addUnchecked(orphan); } catch (err) { this.emit('error', err); continue; @@ -717,8 +731,6 @@ Mempool.prototype.addUnchecked = co(function* addUnchecked(entry, force) { this.logger.spam('Resolved orphan %s in mempool.', orphan.tx.rhash); } - - unlock(); }); /** diff --git a/lib/net/peer.js b/lib/net/peer.js index bac80356..26310a4c 100644 --- a/lib/net/peer.js +++ b/lib/net/peer.js @@ -236,15 +236,6 @@ Peer.prototype._init = function init() { } }; -/** - * Invoke mutex lock. - * @private - */ - -Peer.prototype._lock = function _lock(func, args, force) { - return this.locker.lock(func, args, force); -}; - /** * Handle `connect` event (called immediately * if a socket was passed into peer). @@ -1120,20 +1111,28 @@ Peer.prototype._handleUTXOs = function _handleUTXOs(utxos) { */ Peer.prototype._handleGetUTXOs = co(function* _handleGetUTXOs(packet) { - var unlock = yield this._lock(); + var unlock = yield this.locker.lock(); + try { + return yield this.__handleGetUTXOs(packet); + } finally { + unlock(); + } +}); + +Peer.prototype.__handleGetUTXOs = co(function* _handleGetUTXOs(packet) { var i, utxos, prevout, hash, index, coin; if (!this.chain.synced) - return unlock(); + return; if (this.options.selfish) - return unlock(); + return; if (this.chain.db.options.spv) - return unlock(); + return; if (packet.prevout.length > 15) - return unlock(); + return; utxos = new packets.GetUTXOsPacket(); @@ -1182,7 +1181,6 @@ Peer.prototype._handleGetUTXOs = co(function* _handleGetUTXOs(packet) { utxos.tip = this.chain.tip.hash; this.send(utxos); - unlock(); }); /** @@ -1203,21 +1201,29 @@ Peer.prototype._handleHaveWitness = function _handleHaveWitness(packet) { */ Peer.prototype._handleGetHeaders = co(function* _handleGetHeaders(packet) { - var unlock = yield this._lock(); + var unlock = yield this.locker.lock(); + try { + return this.__handleGetHeaders(packet); + } finally { + unlock(); + } +}); + +Peer.prototype.__handleGetHeaders = co(function* _handleGetHeaders(packet) { var headers = []; var hash, entry; if (!this.chain.synced) - return unlock(); + return; if (this.options.selfish) - return unlock(); + return; if (this.chain.db.options.spv) - return unlock(); + return; if (this.chain.db.options.prune) - return unlock(); + return; if (packet.locator.length > 0) { hash = yield this.chain.findLocator(packet.locator); @@ -1243,8 +1249,6 @@ Peer.prototype._handleGetHeaders = co(function* _handleGetHeaders(packet) { } this.sendHeaders(headers); - - unlock(); }); /** @@ -1254,21 +1258,29 @@ Peer.prototype._handleGetHeaders = co(function* _handleGetHeaders(packet) { */ Peer.prototype._handleGetBlocks = co(function* _handleGetBlocks(packet) { - var unlock = yield this._lock(); + var unlock = yield this.locker.lock(); + try { + return this.__handleGetBlocks(packet); + } finally { + unlock(); + } +}); + +Peer.prototype.__handleGetBlocks = co(function* _handleGetBlocks(packet) { var blocks = []; var hash; if (!this.chain.synced) - return unlock(); + return; if (this.options.selfish) - return unlock(); + return; if (this.chain.db.options.spv) - return unlock(); + return; if (this.chain.db.options.prune) - return unlock(); + return; hash = yield this.chain.findLocator(packet.locator); @@ -1290,7 +1302,6 @@ Peer.prototype._handleGetBlocks = co(function* _handleGetBlocks(packet) { } this.sendInv(blocks); - unlock(); }); /** @@ -1476,7 +1487,15 @@ Peer.prototype._getItem = co(function* _getItem(item) { */ Peer.prototype._handleGetData = co(function* _handleGetData(packet) { - var unlock = yield this._lock(); + var unlock = yield this.locker.lock(); + try { + return yield this.__handleGetData(packet); + } finally { + unlock(); + } +}); + +Peer.prototype._handleGetData = co(function* _handleGetData(packet) { var notFound = []; var items = packet.items; var i, j, item, entry, tx, block; @@ -1580,8 +1599,6 @@ Peer.prototype._handleGetData = co(function* _handleGetData(packet) { if (notFound.length > 0) this.send(new packets.NotFoundPacket(notFound)); - - unlock(); }); /** diff --git a/lib/net/pool.js b/lib/net/pool.js index 1b2dcd8e..0aa8bc7d 100644 --- a/lib/net/pool.js +++ b/lib/net/pool.js @@ -276,15 +276,6 @@ Pool.prototype._init = function _init() { }); }; -/** - * Invoke mutex lock. - * @private - */ - -Pool.prototype._lock = function _lock(force) { - return this.locker.lock(force); -}; - /** * Open the pool, wait for the chain to load. * @alias Pool#open @@ -722,13 +713,20 @@ Pool.prototype.stopSync = function stopSync() { */ Pool.prototype._handleHeaders = co(function* _handleHeaders(headers, peer) { - var i, unlock, ret, header, hash, last; + var unlock = yield this.locker.lock(); + try { + return yield this.__handleHeaders(headers, peer); + } finally { + unlock(); + } +}); + +Pool.prototype.__handleHeaders = co(function* _handleHeaders(headers, peer) { + var i, ret, header, hash, last; if (!this.options.headers) return; - unlock = yield this._lock(); - ret = new VerifyResult(); this.logger.debug( @@ -751,13 +749,11 @@ Pool.prototype._handleHeaders = co(function* _handleHeaders(headers, peer) { if (last && header.prevBlock !== last) { peer.setMisbehavior(100); - unlock(); throw new Error('Bad header chain.'); } if (!header.verify(ret)) { peer.reject(header, 'invalid', ret.reason, 100); - unlock(); throw new Error('Invalid header.'); } @@ -778,8 +774,6 @@ Pool.prototype._handleHeaders = co(function* _handleHeaders(headers, peer) { // the peer's chain. if (last && headers.length === 2000) yield peer.getHeaders(last, null); - - unlock(); }); /** @@ -859,7 +853,15 @@ Pool.prototype._handleBlocks = co(function* _handleBlocks(hashes, peer) { */ Pool.prototype._handleInv = co(function* _handleInv(hashes, peer) { - var unlock = yield this._lock(); + var unlock = yield this.locker.lock(); + try { + return yield this.__handleInv(hashes, peer); + } finally { + unlock(); + } +}); + +Pool.prototype.__handleInv = co(function* _handleInv(hashes, peer) { var i, hash; // Ignore for now if we're still syncing @@ -868,7 +870,6 @@ Pool.prototype._handleInv = co(function* _handleInv(hashes, peer) { if (!this.options.headers) { yield this._handleBlocks(hashes, peer); - unlock(); return; } @@ -878,7 +879,6 @@ Pool.prototype._handleInv = co(function* _handleInv(hashes, peer) { } this.scheduleRequests(peer); - unlock(); }); /** @@ -1048,7 +1048,7 @@ Pool.prototype.createPeer = function createPeer(addr, socket) { self.addLoader(); }); - peer.on('merkleblock', function(block) { + peer.on('merkleblock', co(function *(block) { if (!self.options.spv) return; @@ -1057,17 +1057,20 @@ Pool.prototype.createPeer = function createPeer(addr, socket) { // If the peer sent us a block that was added // to the chain (not orphans), reset the timeout. - self._handleBlock(block, peer).then(function() { - if (peer.isLoader()) { - self.startInterval(); - self.startTimeout(); - } - }, function(err) { + try { + yield self._handleBlock(block, peer); + } catch (e) { self.emit('error', err); - }); - }); + return; + } - peer.on('block', function(block) { + if (peer.isLoader()) { + self.startInterval(); + self.startTimeout(); + } + })); + + peer.on('block', co(function *(block) { if (self.options.spv) return; @@ -1076,15 +1079,18 @@ Pool.prototype.createPeer = function createPeer(addr, socket) { // If the peer sent us a block that was added // to the chain (not orphans), reset the timeout. - self._handleBlock(block, peer).then(function() { - if (peer.isLoader()) { - self.startInterval(); - self.startTimeout(); - } - }, function(err) { - self.emit('error', err); - }); - }); + try { + yield self._handleBlock(block, peer); + } catch (e) { + self.emit('error', e); + return; + } + + if (peer.isLoader()) { + self.startInterval(); + self.startTimeout(); + } + })); peer.on('error', function(err) { self.emit('error', err, peer); @@ -1620,21 +1626,18 @@ Pool.prototype.exists = function exists(type, hash) { * @param {Peer} peer */ -Pool.prototype.scheduleRequests = function scheduleRequests(peer) { - var self = this; - +Pool.prototype.scheduleRequests = co(function* scheduleRequests(peer) { if (this.scheduled) return; this.scheduled = true; - this.chain.onDrain().then(function() { - utils.nextTick(function() { - self.sendRequests(peer); - self.scheduled = false; - }); - }); -}; + yield this.chain.onDrain(); + yield spawn.wait(); + + this.sendRequests(peer); + this.scheduled = false; +}); /** * Send scheduled requests in the request queues. diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index 38b793dd..639b0b86 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -253,16 +253,6 @@ TXDB.prototype.emit = function emit(event, tx, info) { this.wallet.emit(event, tx, info); }; -/** - * Invoke the mutex lock. - * @private - * @returns {Function} unlock - */ - -TXDB.prototype._lock = function _lock(force) { - return this.locker.lock(force); -}; - /** * Prefix a key. * @param {Buffer} key @@ -594,7 +584,15 @@ TXDB.prototype._resolveOrphans = co(function* _resolveOrphans(tx, index) { */ TXDB.prototype.add = co(function* add(tx, info) { - var unlock = yield this._lock(); + var unlock = yield this.locker.lock(); + try { + return yield this.addl(tx, info); + } finally { + unlock(); + } +}); + +TXDB.prototype.addl = co(function* add(tx, info) { var hash, path, account; var i, result, input, output, coin; var prevout, key, address, spender, orphans; @@ -605,18 +603,14 @@ TXDB.prototype.add = co(function* add(tx, info) { result = yield this._confirm(tx, info); // Ignore if we already have this tx. - if (result) { - unlock(); + if (result) return true; - } // Verify and get coins. result = yield this._verify(tx, info); - if (!result) { - unlock(); + if (!result) return false; - } hash = tx.hash('hex'); @@ -667,7 +661,6 @@ TXDB.prototype.add = co(function* add(tx, info) { yield this._addOrphan(prevout, spender); } catch (e) { this.drop(); - unlock(); throw e; } continue; @@ -697,7 +690,6 @@ TXDB.prototype.add = co(function* add(tx, info) { orphans = yield this._resolveOrphans(tx, i); } catch (e) { this.drop(); - unlock(); throw e; } @@ -714,12 +706,7 @@ TXDB.prototype.add = co(function* add(tx, info) { this.coinCache.set(key, coin); } - try { - yield this.commit(); - } catch (e) { - unlock(); - throw e; - } + yield this.commit(); // Clear any locked coins to free up memory. this.unlockTX(tx); @@ -729,7 +716,6 @@ TXDB.prototype.add = co(function* add(tx, info) { if (tx.ts !== 0) this.emit('confirmed', tx, info); - unlock(); return true; }); @@ -964,11 +950,23 @@ TXDB.prototype._confirm = co(function* _confirm(tx, info) { * @param {Function} callback - Returns [Error]. */ -TXDB.prototype.remove = co(function* remove(hash, force) { - var unlock = yield this._lock(force); - var info = yield this._removeRecursive(); +TXDB.prototype.remove = co(function* remove(hash) { + var unlock = yield this.locker.lock(); + try { + return yield this.removel(hash); + } finally { + unlock(); + } +}); - unlock(); +TXDB.prototype.removel = co(function* remove(hash) { + var tx = yield this.getTX(hash); + var info; + + if (!tx) + return; + + info = yield this._removeRecursive(tx); if (!info) return; @@ -1087,33 +1085,26 @@ TXDB.prototype._remove = co(function* remove(tx, info) { * @param {Function} callback */ -TXDB.prototype.unconfirm = co(function* unconfirm(hash, force) { - var unlock = yield this._lock(force); - var tx, info, result; - +TXDB.prototype.unconfirm = co(function* unconfirm(hash) { + var unlock = yield this.locker.lock(); try { - tx = yield this.getTX(hash); - } catch (e) { + return yield this.unconfirml(hash); + } finally { unlock(); - throw e; } +}); - if (!tx) { - unlock(); +TXDB.prototype.unconfirml = co(function* unconfirm(hash) { + var tx = yield this.getTX(hash); + var info, result; + + if (!tx) return false; - } - try { - info = yield this.getInfo(tx); - } catch (e) { - unlock(); - throw e; - } + info = yield this.getInfo(tx); - if (!info) { - unlock(); + if (!info) return false; - } this.start(); @@ -1121,18 +1112,11 @@ TXDB.prototype.unconfirm = co(function* unconfirm(hash, force) { result = yield this._unconfirm(tx, info); } catch (e) { this.drop(); - unlock(); throw e; } - try { - yield this.commit(); - } catch (e) { - unlock(); - throw e; - } + yield this.commit(); - unlock(); return result; }); @@ -1943,7 +1927,15 @@ TXDB.prototype.getAccountBalance = co(function* getBalance(account) { */ TXDB.prototype.zap = co(function* zap(account, age) { - var unlock = yield this._lock(); + var unlock = yield this.locker.lock(); + try { + return yield this._zap(account, age); + } finally { + unlock(); + } +}); + +TXDB.prototype._zap = co(function* zap(account, age) { var i, txs, tx, hash; if (!utils.isUInt32(age)) @@ -1961,15 +1953,8 @@ TXDB.prototype.zap = co(function* zap(account, age) { if (tx.ts !== 0) continue; - try { - yield this.remove(hash, true); - } catch (e) { - unlock(); - throw e; - } + yield this.removel(hash); } - - unlock(); }); /** diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js index 792ea13d..76b723c6 100644 --- a/lib/wallet/wallet.js +++ b/lib/wallet/wallet.js @@ -77,24 +77,6 @@ function Wallet(db, options) { utils.inherits(Wallet, EventEmitter); -/** - * Invoke write mutex lock. - * @private - */ - -Wallet.prototype._lockWrite = function _lockWrite(force) { - return this.writeLock.lock(force); -}; - -/** - * Invoke funding mutex lock. - * @private - */ - -Wallet.prototype._lockFund = function _lockFund(force) { - return this.fundLock.lock(force); -}; - /** * Inject properties from options object. * @private @@ -246,7 +228,15 @@ Wallet.prototype.destroy = function destroy() { */ Wallet.prototype.addKey = co(function* addKey(account, key) { - var unlock = yield this._lockWrite(); + var unlock = yield this.writeLock.lock(); + try { + return yield this._addKey(account, key); + } finally { + unlock(); + } +}); + +Wallet.prototype._addKey = co(function* addKey(account, key) { var result; if (!key) { @@ -259,10 +249,8 @@ Wallet.prototype.addKey = co(function* addKey(account, key) { account = yield this.getAccount(account); - if (!account) { - unlock(); + if (!account) throw new Error('Account not found.'); - } this.start(); @@ -270,13 +258,11 @@ Wallet.prototype.addKey = co(function* addKey(account, key) { result = yield account.addKey(key, true); } catch (e) { this.drop(); - unlock(); throw e; } yield this.commit(); - unlock(); return result; }); @@ -288,7 +274,15 @@ Wallet.prototype.addKey = co(function* addKey(account, key) { */ Wallet.prototype.removeKey = co(function* removeKey(account, key) { - var unlock = yield this._lockWrite(); + var unlock = yield this.writeLock.lock(); + try { + return yield this._removeKey(account, key); + } finally { + unlock(); + } +}); + +Wallet.prototype._removeKey = co(function* removeKey(account, key) { var result; if (!key) { @@ -301,10 +295,8 @@ Wallet.prototype.removeKey = co(function* removeKey(account, key) { account = yield this.getAccount(account); - if (!account) { - unlock(); + if (!account) throw new Error('Account not found.'); - } this.start(); @@ -312,13 +304,11 @@ Wallet.prototype.removeKey = co(function* removeKey(account, key) { result = yield account.removeKey(key, true); } catch (e) { this.drop(); - unlock(); throw e; } yield this.commit(); - unlock(); return result; }); @@ -330,27 +320,30 @@ Wallet.prototype.removeKey = co(function* removeKey(account, key) { */ Wallet.prototype.setPassphrase = co(function* setPassphrase(old, new_) { - var unlock = yield this._lockWrite(); + var unlock = yield this.writeLock.lock(); + try { + return yield this._setPassphrase(old, new_); + } finally { + unlock(); + } +}); - if (!new_) { +Wallet.prototype._setPassphrase = co(function* setPassphrase(old, new_) { + if (new_ == null) { new_ = old; old = null; } - try { - if (old) - yield this.master.decrypt(old); - if (new_) - yield this.master.encrypt(new_); - } catch (e) { - unlock(); - throw e; - } + if (old) + yield this.master.decrypt(old); + + if (new_) + yield this.master.encrypt(new_); this.start(); this.save(); + yield this.commit(); - unlock(); }); /** @@ -360,24 +353,25 @@ Wallet.prototype.setPassphrase = co(function* setPassphrase(old, new_) { */ Wallet.prototype.retoken = co(function* retoken(passphrase) { - var unlock = yield this._lockWrite(); - var master; - + var unlock = yield this.writeLock.lock(); try { - master = yield this.unlock(passphrase); - } catch (e) { + return yield this._retoken(passphrase); + } finally { unlock(); - throw e; } +}); + +Wallet.prototype._retoken = co(function* retoken(passphrase) { + var master = yield this.unlock(passphrase); this.tokenDepth++; this.token = this.getToken(master, this.tokenDepth); this.start(); this.save(); + yield this.commit(); - unlock(); return this.token; }); @@ -461,7 +455,15 @@ Wallet.prototype.getToken = function getToken(master, nonce) { */ Wallet.prototype.createAccount = co(function* createAccount(options) { - var unlock = yield this._lockWrite(); + var unlock = yield this.writeLock.lock(); + try { + return yield this._createAccount(options); + } finally { + unlock(); + } +}); + +Wallet.prototype._createAccount = co(function* createAccount(options) { var passphrase = options.passphrase; var timeout = options.timeout; var name = options.name; @@ -473,12 +475,7 @@ Wallet.prototype.createAccount = co(function* createAccount(options) { if (!name) name = this.accountDepth + ''; - try { - master = yield this.unlock(passphrase, timeout); - } catch (e) { - unlock(); - throw e; - } + master = yield this.unlock(passphrase, timeout); key = master.deriveAccount44(this.accountDepth); @@ -502,14 +499,13 @@ Wallet.prototype.createAccount = co(function* createAccount(options) { account = yield this.db.createAccount(options); } catch (e) { this.drop(); - unlock(); throw e; } this.accountDepth++; this.save(); + yield this.commit(); - unlock(); return account; }); @@ -614,7 +610,15 @@ Wallet.prototype.createChange = function createChange(account) { */ Wallet.prototype.createAddress = co(function* createAddress(account, change) { - var unlock = yield this._lockWrite(); + var unlock = yield this.writeLock.lock(); + try { + return yield this._createAddress(account, change); + } finally { + unlock(); + } +}); + +Wallet.prototype._createAddress = co(function* createAddress(account, change) { var result; if (typeof account === 'boolean') { @@ -625,30 +629,21 @@ Wallet.prototype.createAddress = co(function* createAddress(account, change) { if (account == null) account = 0; - try { - account = yield this.getAccount(account); - } catch (e) { - unlock(); - throw e; - } + account = yield this.getAccount(account); - if (!account) { - unlock(); + if (!account) throw new Error('Account not found.'); - } this.start(); try { - result = yield account.createAddress(change, true); + result = yield account.createAddress(change); } catch (e) { this.drop(); - unlock(); throw e; } yield this.commit(); - unlock(); return result; }); @@ -760,7 +755,15 @@ Wallet.prototype.getPaths = co(function* getPaths(account) { */ Wallet.prototype.importKey = co(function* importKey(account, ring, passphrase) { - var unlock = yield this._lockWrite(); + var unlock = yield this.writeLock.lock(); + try { + return yield this._importKey(account, ring, passphrase); + } finally { + unlock(); + } +}); + +Wallet.prototype._importKey = co(function* importKey(account, ring, passphrase) { var exists, raw, path; if (account && typeof account === 'object') { @@ -772,36 +775,20 @@ Wallet.prototype.importKey = co(function* importKey(account, ring, passphrase) { if (account == null) account = 0; - try { - exists = yield this.getPath(ring.getHash('hex')); - } catch (e) { - unlock(); - throw e; - } + exists = yield this.getPath(ring.getHash('hex')); - if (exists) { - unlock(); + if (exists) throw new Error('Key already exists.'); - } account = yield this.getAccount(account); - if (!account) { - unlock(); + if (!account) throw new Error('Account not found.'); - } - if (account.type !== bcoin.account.types.PUBKEYHASH) { - unlock(); + if (account.type !== bcoin.account.types.PUBKEYHASH) throw new Error('Cannot import into non-pkh account.'); - } - try { - yield this.unlock(passphrase); - } catch (e) { - unlock(); - throw e; - } + yield this.unlock(passphrase); raw = ring.toRaw(); path = Path.fromAccount(account, ring); @@ -821,12 +808,10 @@ Wallet.prototype.importKey = co(function* importKey(account, ring, passphrase) { yield account.saveAddress([ring], true); } catch (e) { this.drop(); - unlock(); throw e; } yield this.commit(); - unlock(); }); /** @@ -854,31 +839,33 @@ Wallet.prototype.importKey = co(function* importKey(account, ring, passphrase) { */ Wallet.prototype.fund = co(function* fund(tx, options, force) { - var unlock = yield this._lockFund(force); + var unlock = yield this.fundLock.lock(force); + try { + return yield this._fund(tx, options); + } finally { + unlock(); + } +}); + +Wallet.prototype._fund = co(function* fund(tx, options, force) { var rate, account, coins; if (!options) options = {}; - if (!this.initialized) { - unlock(); + if (!this.initialized) throw new Error('Wallet is not initialized.'); - } if (options.account != null) { account = yield this.getAccount(options.account); - if (!account) { - unlock(); + if (!account) throw new Error('Account not found.'); - } } else { account = this.account; } - if (!account.initialized) { - unlock(); + if (!account.initialized) throw new Error('Account is not initialized.'); - } coins = yield this.getCoins(options.account); @@ -894,26 +881,22 @@ Wallet.prototype.fund = co(function* fund(tx, options, force) { // Don't use any locked coins. coins = this.tx.filterLocked(coins); - try { - tx.fund(coins, { - selection: options.selection, - round: options.round, - confirmations: options.confirmations, - free: options.free, - hardFee: options.hardFee, - subtractFee: options.subtractFee, - changeAddress: account.changeAddress.getAddress(), - height: this.db.height, - rate: rate, - maxFee: options.maxFee, - m: account.m, - n: account.n, - witness: account.witness, - script: account.receiveAddress.script - }); - } finally { - unlock(); - } + tx.fund(coins, { + selection: options.selection, + round: options.round, + confirmations: options.confirmations, + free: options.free, + hardFee: options.hardFee, + subtractFee: options.subtractFee, + changeAddress: account.changeAddress.getAddress(), + height: this.db.height, + rate: rate, + maxFee: options.maxFee, + m: account.m, + n: account.n, + witness: account.witness, + script: account.receiveAddress.script + }); }); /** @@ -977,27 +960,21 @@ Wallet.prototype.createTX = co(function* createTX(options, force) { */ Wallet.prototype.send = co(function* send(options) { - var unlock = yield this._lockFund(); - var tx; - + var unlock = yield this.fundLock.lock(); try { - tx = yield this.createTX(options, true); - } catch (e) { + return yield this._send(options); + } finally { unlock(); - throw e; } +}); - try { - yield this.sign(tx); - } catch (e) { - unlock(); - throw e; - } +Wallet.prototype._send = co(function* send(options) { + var tx = yield this.createTX(options, true); - if (!tx.isSigned()) { - unlock(); + yield this.sign(tx); + + if (!tx.isSigned()) throw new Error('TX could not be fully signed.'); - } tx = tx.toTX(); @@ -1006,7 +983,6 @@ Wallet.prototype.send = co(function* send(options) { this.logger.debug('Sending wallet tx (%s): %s', this.id, tx.rhash); this.db.emit('send', tx); - unlock(); return tx; }); @@ -1160,7 +1136,15 @@ Wallet.prototype.getOutputPaths = co(function* getOutputPaths(tx) { */ Wallet.prototype.syncOutputDepth = co(function* syncOutputDepth(info) { - var unlock = yield this._lockWrite(); + var unlock = yield this.writeLock.lock(); + try { + return yield this._syncOutputDepth(info); + } finally { + unlock(); + } +}); + +Wallet.prototype._syncOutputDepth = co(function* syncOutputDepth(info) { var receive = []; var accounts = {}; var i, j, path, paths, account; @@ -1204,22 +1188,12 @@ Wallet.prototype.syncOutputDepth = co(function* syncOutputDepth(info) { receiveDepth += 2; changeDepth += 2; - try { - account = yield this.getAccount(account); - } catch (e) { - unlock(); - throw e; - } + account = yield this.getAccount(account); if (!account) continue; - try { - ret = yield account.setDepth(receiveDepth, changeDepth); - } catch (e) { - unlock(); - throw e; - } + ret = yield account.setDepth(receiveDepth, changeDepth); rcv = ret[0]; chng = ret[1]; @@ -1235,7 +1209,6 @@ Wallet.prototype.syncOutputDepth = co(function* syncOutputDepth(info) { yield this.commit(); - unlock(); return receive; }); @@ -2002,15 +1975,6 @@ MasterKey.fromOptions = function fromOptions(options) { return new MasterKey().fromOptions(options); }; -/** - * Invoke mutex lock. - * @private - */ - -MasterKey.prototype._lock = function _lock(force) { - return this.locker.lock(force); -}; - /** * Decrypt the key and set a timeout to destroy decrypted data. * @param {Buffer|String} passphrase - Zero this yourself. @@ -2019,28 +1983,27 @@ MasterKey.prototype._lock = function _lock(force) { */ MasterKey.prototype.unlock = co(function* _unlock(passphrase, timeout) { - var unlock = yield this._lock(); + var unlock = yield this.locker.lock(); + try { + return yield this._unlock(passphrase, timeout); + } finally { + unlock(); + } +}); + +MasterKey.prototype._unlock = co(function* _unlock(passphrase, timeout) { var data, key; - if (this.key) { - unlock(); + if (this.key) return this.key; - } - if (!passphrase) { - unlock(); + if (!passphrase) throw new Error('No passphrase.'); - } assert(this.encrypted); - try { - key = yield crypto.derive(passphrase); - data = crypto.decipher(this.ciphertext, key, this.iv); - } catch (e) { - unlock(); - throw e; - } + key = yield crypto.derive(passphrase); + data = crypto.decipher(this.ciphertext, key, this.iv); this.key = bcoin.hd.fromExtended(data); @@ -2048,7 +2011,6 @@ MasterKey.prototype.unlock = co(function* _unlock(passphrase, timeout) { this.aesKey = key; - unlock(); return this.key; }); @@ -2137,38 +2099,33 @@ MasterKey.prototype.destroy = function destroy() { */ MasterKey.prototype.decrypt = co(function* decrypt(passphrase) { - var unlock = yield this._lock(); + var unlock = yield this.locker.lock(); + try { + return yield this._decrypt(passphrase); + } finally { + unlock(); + } +}); + +MasterKey.prototype._decrypt = co(function* decrypt(passphrase) { var data; if (!this.encrypted) { assert(this.key); - return unlock(); + return; } if (!passphrase) - return unlock(); + return; this.destroy(); - try { - data = yield crypto.decrypt(this.ciphertext, passphrase, this.iv); - } catch (e) { - unlock(); - throw e; - } - - try { - this.key = bcoin.hd.fromExtended(data); - } catch (e) { - unlock(); - throw e; - } + data = yield crypto.decrypt(this.ciphertext, passphrase, this.iv); + this.key = bcoin.hd.fromExtended(data); this.encrypted = false; this.iv = null; this.ciphertext = null; - - unlock(); }); /** @@ -2178,33 +2135,34 @@ MasterKey.prototype.decrypt = co(function* decrypt(passphrase) { */ MasterKey.prototype.encrypt = co(function* encrypt(passphrase) { - var unlock = yield this._lock(); + var unlock = yield this.locker.lock(); + try { + return yield this._encrypt(passphrase); + } finally { + unlock(); + } +}); + +MasterKey.prototype._encrypt = co(function* encrypt(passphrase) { var data, iv; if (this.encrypted) - return unlock(); + return; if (!passphrase) - return unlock(); + return; data = this.key.toExtended(); iv = crypto.randomBytes(16); this.stop(); - try { - data = yield crypto.encrypt(data, passphrase, iv); - } catch (e) { - unlock(); - throw e; - } + data = yield crypto.encrypt(data, passphrase, iv); this.key = null; this.encrypted = true; this.iv = iv; this.ciphertext = data; - - unlock(); }); /** diff --git a/lib/wallet/walletdb.js b/lib/wallet/walletdb.js index 6b48a496..d8e15c04 100644 --- a/lib/wallet/walletdb.js +++ b/lib/wallet/walletdb.js @@ -193,33 +193,6 @@ WalletDB.prototype._init = function _init() { } }; -/** - * Invoke wallet read mutex lock. - * @private - */ - -WalletDB.prototype._lockRead = function _lockRead(key, force) { - return this.readLock.lock(key, force); -}; - -/** - * Invoke wallet write mutex lock. - * @private - */ - -WalletDB.prototype._lockWrite = function _lockWrite(key, force) { - return this.writeLock.lock(key, force); -}; - -/** - * Invoke tx handling mutex lock. - * @private - */ - -WalletDB.prototype._lockTX = function _lockTX(force) { - return this.txLock.lock(force); -}; - /** * Open the walletdb, wait for the database to load. * @alias WalletDB#open @@ -474,7 +447,6 @@ WalletDB.prototype.getWalletID = function getWalletID(id) { */ WalletDB.prototype.get = co(function* get(wid) { - var self = this; var wallet, unlock; wid = yield this.getWalletID(wid); @@ -486,31 +458,30 @@ WalletDB.prototype.get = co(function* get(wid) { return wallet; // NOTE: Lock must start here! - unlock = yield this._lockRead(wid); + unlock = yield this.readLock.lock(wid); try { - wallet = yield this.db.fetch(layout.w(wid), function(data) { - return bcoin.wallet.fromRaw(self, data); - }); - } catch (e) { + return yield this._get(wid); + } finally { unlock(); - throw e; } +}); - if (!wallet) { - unlock(); +WalletDB.prototype._get = co(function* get(wid) { + var self = this; + var wallet; + + wallet = yield this.db.fetch(layout.w(wid), function(data) { + return bcoin.wallet.fromRaw(self, data); + }); + + if (!wallet) return; - } - try { - this.register(wallet); - yield wallet.open(); - } catch (e) { - unlock(); - throw e; - } + this.register(wallet); + + yield wallet.open(); - unlock(); return wallet; }); @@ -561,33 +532,36 @@ WalletDB.prototype.auth = co(function* auth(wid, token) { */ WalletDB.prototype.create = co(function* create(options) { - var unlock, wallet, exists; + var unlock; if (!options) options = {}; - unlock = yield this._lockWrite(options.id); - - exists = yield this.has(options.id); - - if (exists) { - unlock(); - throw new Error('Wallet already exists.'); - } + unlock = yield this.writeLock.lock(options.id); try { - wallet = bcoin.wallet.fromOptions(this, options); - wallet.wid = this.depth++; - this.register(wallet); - yield wallet.init(options); - } catch (e) { + return yield this._create(options); + } finally { unlock(); - throw e; } +}); + +WalletDB.prototype._create = co(function* create(options) { + var exists = yield this.has(options.id); + var wallet; + + if (exists) + throw new Error('Wallet already exists.'); + + wallet = bcoin.wallet.fromOptions(this, options); + wallet.wid = this.depth++; + + this.register(wallet); + + yield wallet.init(options); this.logger.info('Created wallet %s.', wallet.id); - unlock(); return wallet; }); @@ -963,8 +937,16 @@ WalletDB.prototype.getWallets = function getWallets() { */ WalletDB.prototype.rescan = co(function* rescan(chaindb, height) { + var unlock = yield this.txLock.lock(); + try { + return yield this._rescan(chaindb, height); + } finally { + unlock(); + } +}); + +WalletDB.prototype._rescan = co(function* rescan(chaindb, height) { var self = this; - var unlock = yield this._lockTX(); var hashes; if (height == null) @@ -974,16 +956,9 @@ WalletDB.prototype.rescan = co(function* rescan(chaindb, height) { this.logger.info('Scanning for %d addresses.', hashes.length); - try { - yield chaindb.scan(height, hashes, co(function *(block, txs) { - yield self.addBlock(block, txs, true); - })); - } catch (e) { - unlock(); - throw e; - } - - unlock(); + yield chaindb.scan(height, hashes, co(function *(block, txs) { + yield self._addBlock(block, txs); + })); }); /** @@ -1235,19 +1210,21 @@ WalletDB.prototype.getWalletsByTX = function getWalletsByTX(hash) { * @param {Function} callback */ -WalletDB.prototype.addBlock = co(function* addBlock(entry, txs, force) { - var unlock = yield this._lockTX(force); +WalletDB.prototype.addBlock = co(function* addBlock(entry, txs) { + var unlock = yield this.txLock.lock(); + try { + return yield this._addBlock(entry, txs); + } finally { + unlock(); + } +}); + +WalletDB.prototype._addBlock = co(function* addBlock(entry, txs) { var i, block, matches, hash, tx, wallets; if (this.options.useCheckpoints) { if (entry.height <= this.network.checkpoints.lastHeight) { - try { - yield this.setTip(entry.hash, entry.height); - } catch (e) { - unlock(); - throw e; - } - unlock(); + yield this.setTip(entry.hash, entry.height); return; } } @@ -1266,12 +1243,7 @@ WalletDB.prototype.addBlock = co(function* addBlock(entry, txs, force) { for (i = 0; i < txs.length; i++) { tx = txs[i]; - try { - wallets = yield this.addTX(tx, true); - } catch (e) { - unlock(); - throw e; - } + wallets = yield this._addTX(tx); if (!wallets) continue; @@ -1286,14 +1258,7 @@ WalletDB.prototype.addBlock = co(function* addBlock(entry, txs, force) { utils.revHex(block.hash), block.hashes.length); } - try { - yield this.writeBlock(block, matches); - } catch (e) { - unlock(); - throw e; - } - - unlock(); + yield this.writeBlock(block, matches); }); /** @@ -1304,7 +1269,15 @@ WalletDB.prototype.addBlock = co(function* addBlock(entry, txs, force) { */ WalletDB.prototype.removeBlock = co(function* removeBlock(entry) { - var unlock = yield this._lockTX(); + var unlock = yield this.txLock.lock(); + try { + return yield this._removeBlock(entry, txs); + } finally { + unlock(); + } +}); + +WalletDB.prototype._removeBlock = co(function* removeBlock(entry) { var i, j, block, data, hash, wallets, wid, wallet; block = WalletBlock.fromEntry(entry); @@ -1349,8 +1322,6 @@ WalletDB.prototype.removeBlock = co(function* removeBlock(entry) { this.tip = block.hash; this.height = block.height; - - unlock(); }); /** @@ -1362,7 +1333,15 @@ WalletDB.prototype.removeBlock = co(function* removeBlock(entry) { */ WalletDB.prototype.addTX = co(function* addTX(tx, force) { - var unlock = yield this._lockTX(force); + var unlock = yield this.txLock.lock(); + try { + return yield this._addTX(tx); + } finally { + unlock(); + } +}); + +WalletDB.prototype._addTX = co(function* addTX(tx, force) { var i, wallets, info, wallet; assert(!tx.mutable, 'Cannot add mutable TX to wallet.'); @@ -1371,17 +1350,10 @@ WalletDB.prototype.addTX = co(function* addTX(tx, force) { // Atomicity doesn't matter here. If we crash, // the automatic rescan will get the database // back in the correct state. - try { - wallets = yield this.mapWallets(tx); - } catch (e) { - unlock(); - throw e; - } + wallets = yield this.mapWallets(tx); - if (!wallets) { - unlock(); + if (!wallets) return; - } this.logger.info( 'Incoming transaction for %d wallets (%s).', @@ -1398,16 +1370,10 @@ WalletDB.prototype.addTX = co(function* addTX(tx, force) { info.id = wallet.id; - try { - yield wallet.tx.add(tx, info); - yield wallet.handleTX(info); - } catch (e) { - unlock(); - throw e; - } + yield wallet.tx.add(tx, info); + yield wallet.handleTX(info); } - unlock(); return wallets; });