diff --git a/lib/bcoin/input.js b/lib/bcoin/input.js index eba0d476..e7ae4872 100644 --- a/lib/bcoin/input.js +++ b/lib/bcoin/input.js @@ -442,7 +442,6 @@ Input.prototype.inspect = function inspect() { type: this.getType(), subtype: this.getSubtype(), address: this.getAddress(), - value: utils.btc(coin.value), script: this.script, witness: this.witness, redeem: this.getRedeem(), diff --git a/lib/bcoin/locker.js b/lib/bcoin/locker.js index 7ef0cead..a6aa1a3e 100644 --- a/lib/bcoin/locker.js +++ b/lib/bcoin/locker.js @@ -21,7 +21,9 @@ var assert = utils.assert; function Locker(parent, add) { if (!(this instanceof Locker)) - return Locker(parent, add); + return new Locker(parent, add); + + EventEmitter.call(this); this.parent = parent; this.jobs = []; diff --git a/lib/bcoin/walletdb.js b/lib/bcoin/walletdb.js index 0fa5dcb8..d4db8c8f 100644 --- a/lib/bcoin/walletdb.js +++ b/lib/bcoin/walletdb.js @@ -50,6 +50,10 @@ function WalletDB(options) { this.options = options; this.network = bcoin.network.get(options.network); + // We need one read lock for `get` and `create`. + // It will hold locks specific to wallet ids. + this.readLock = new ReadLock(this); + this.db = bcoin.ldb({ network: this.network, name: this.options.name || 'wallet', @@ -156,6 +160,15 @@ WalletDB.prototype._close = function close(callback) { }); }; +/** + * Invoke mutex lock. + * @returns {Function} unlock + */ + +WalletDB.prototype._lock = function lock(id, func, args, force) { + return this.readLock.lock(id, func, args, force); +}; + /** * Emit balance events after a tx is saved. * @private @@ -398,7 +411,14 @@ WalletDB.prototype.hasListener = function hasListener(id, event) { WalletDB.prototype.get = function get(id, callback) { var self = this; - var watcher, wallet; + var unlock, watcher, wallet; + + unlock = this._lock(id, get, [id, callback]); + + if (!unlock) + return; + + callback = utils.wrap(callback, unlock); if (!id) return callback(); @@ -459,13 +479,20 @@ WalletDB.prototype.save = function save(wallet, callback) { WalletDB.prototype.create = function create(options, callback) { var self = this; - var wallet; + var wallet, unlock; if (typeof options === 'function') { callback = options; options = {}; } + unlock = this._lock(options.id, create, [options, callback]); + + if (!unlock) + return; + + callback = utils.wrap(callback, unlock); + this.has(options.id, function(err, exists) { if (err) return callback(err); @@ -1049,9 +1076,6 @@ WalletDB.prototype._getKey = function _getKey(id, account, errback, callback) { WalletDB.prototype.removeBlockSPV = function removeBlockSPV(block, callback) { var self = this; - - callback = utils.ensure(callback); - this.tx.getHeightHashes(block.height, function(err, hashes) { if (err) return callback(err); @@ -1071,9 +1095,6 @@ WalletDB.prototype.removeBlockSPV = function removeBlockSPV(block, callback) { WalletDB.prototype.removeBlock = function removeBlock(block, callback) { var self = this; - - callback = utils.ensure(callback); - utils.forEachSerial(block.txs, function(tx, next) { self.tx.unconfirm(tx.hash('hex'), next); }, callback); @@ -1438,6 +1459,51 @@ function isAlpha(key) { return /^[\u0030-\u007d]+$/.test(key); } +function ReadLock(parent) { + if (!(this instanceof ReadLock)) + return new ReadLock(parent); + + this.parent = parent; + this.jobs = []; + this.busy = {}; +} + +ReadLock.prototype.lock = function lock(id, func, args, force) { + var self = this; + var called; + + if (force || !id) { + assert(!id || this.busy[id]); + return function unlock() { + assert(!called); + called = true; + }; + } + + if (this.busy[id]) { + this.jobs.push([func, args]); + return; + } + + this.busy[id] = true; + + return function unlock() { + var item; + + assert(!called); + called = true; + + delete self.busy[id]; + + if (self.jobs.length === 0) + return; + + item = self.jobs.shift(); + + item[0].apply(self.parent, item[1]); + }; +}; + /* * Expose */