refactor: locks.

This commit is contained in:
Christopher Jeffrey 2016-09-22 05:24:18 -07:00
parent 8cb6faa078
commit 4e11bbbf9f
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
7 changed files with 479 additions and 561 deletions

View File

@ -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;
});

View File

@ -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();
});
/**

View File

@ -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();
});
/**

View File

@ -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.

View File

@ -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();
});
/**

View File

@ -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();
});
/**

View File

@ -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;
});