txdb: refactor. clean up.

This commit is contained in:
Christopher Jeffrey 2016-08-12 02:04:59 -07:00
parent c582a0fad8
commit 63c9c81831
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
2 changed files with 211 additions and 155 deletions

View File

@ -284,10 +284,10 @@ config.parseArg = function parseArg(argv) {
if (arg[0] === '-') { if (arg[0] === '-') {
// e.g. -abc // e.g. -abc
arg = arg.substring(1).split(''); arg = arg.substring(1);
for (i = 0; i < arg.length; i++) { for (i = 0; i < arg.length; i++) {
key = arg[i].trim(); key = arg[i];
alias = config.alias.arg[key]; alias = config.alias.arg[key];
if (alias) if (alias)
key = alias; key = alias;

View File

@ -64,6 +64,7 @@ function TXDB(db, options) {
this.busy = false; this.busy = false;
this.jobs = []; this.jobs = [];
this.locker = new bcoin.locker(this); this.locker = new bcoin.locker(this);
this.current = null;
this.coinCache = new bcoin.lru(10000, 1); this.coinCache = new bcoin.lru(10000, 1);
@ -90,6 +91,77 @@ TXDB.prototype._lock = function _lock(func, args, force) {
return this.locker.lock(func, args, force); return this.locker.lock(func, args, force);
}; };
/**
* Start a batch.
* @returns {Batch}
*/
TXDB.prototype.start = function start() {
assert(!this.current);
this.current = this.db.batch();
return this.current;
};
/**
* Put key and value to current batch.
* @param {String} key
* @param {Buffer} value
*/
TXDB.prototype.put = function put(key, value) {
assert(this.current);
this.current.put(key, value);
};
/**
* Delete key from current batch.
* @param {String} key
*/
TXDB.prototype.del = function del(key) {
assert(this.current);
this.current.del(key);
};
/**
* Get current batch.
* @returns {Batch}
*/
TXDB.prototype.batch = function batch() {
assert(this.current);
return this.current;
};
/**
* Drop current batch.
* @returns {Batch}
*/
TXDB.prototype.drop = function drop() {
assert(this.current);
this.current.clear();
this.current = null;
};
/**
* Commit current batch.
* @param {Function} callback
*/
TXDB.prototype.commit = function commit(callback) {
var self = this;
assert(this.current);
this.current.write(function(err) {
if (err) {
self.current = null;
return callback(err);
}
self.current = null;
return callback();
});
};
/** /**
* Load the bloom filter into memory. * Load the bloom filter into memory.
* @private * @private
@ -146,7 +218,7 @@ TXDB.prototype.getInfo = function getInfo(tx, callback) {
if (!this.testFilter(addresses)) if (!this.testFilter(addresses))
return callback(); return callback();
this.mapAddresses(addresses, function(err, table) { this.getTable(addresses, function(err, table) {
if (err) if (err)
return callback(err); return callback(err);
@ -160,12 +232,12 @@ TXDB.prototype.getInfo = function getInfo(tx, callback) {
}; };
/** /**
* Map address hashes to a wallet ID. * Map address hashes to paths.
* @param {Hash[]} address - Address hashes. * @param {Hash[]} address - Address hashes.
* @param {Function} callback - Returns [Error, {@link AddressTable}]. * @param {Function} callback - Returns [Error, {@link AddressTable}].
*/ */
TXDB.prototype.mapAddresses = function mapAddresses(address, callback) { TXDB.prototype.getTable = function getTable(address, callback) {
var self = this; var self = this;
var table = {}; var table = {};
var count = 0; var count = 0;
@ -217,20 +289,21 @@ TXDB.prototype.mapAddresses = function mapAddresses(address, callback) {
TXDB.prototype._addOrphan = function _addOrphan(key, outpoint, callback) { TXDB.prototype._addOrphan = function _addOrphan(key, outpoint, callback) {
var self = this; var self = this;
var batch = this.batch();
var p; var p;
this.db.get('o/' + key, function(err, buf) { this.db.get('o/' + key, function(err, data) {
if (err) if (err)
return callback(err); return callback(err);
p = new BufferWriter(); p = new BufferWriter();
if (buf) if (data)
p.writeBytes(buf); p.writeBytes(data);
p.writeBytes(outpoint); p.writeBytes(outpoint);
self.batch().put('o/' + key, p.render()); batch.put('o/' + key, p.render());
return callback(); return callback();
}); });
@ -245,10 +318,10 @@ TXDB.prototype._addOrphan = function _addOrphan(key, outpoint, callback) {
TXDB.prototype._getOrphans = function _getOrphans(key, callback) { TXDB.prototype._getOrphans = function _getOrphans(key, callback) {
var self = this; var self = this;
var out = []; var items = [];
this.db.fetch('o/' + key, function(buf) { this.db.fetch('o/' + key, function(data) {
var p = new BufferReader(buf); var p = new BufferReader(data);
var orphans = []; var orphans = [];
while (p.left()) while (p.left())
@ -267,7 +340,7 @@ TXDB.prototype._getOrphans = function _getOrphans(key, callback) {
if (err) if (err)
return next(err); return next(err);
out.push([orphan, tx]); items.push([orphan, tx]);
next(); next();
}); });
@ -275,7 +348,7 @@ TXDB.prototype._getOrphans = function _getOrphans(key, callback) {
if (err) if (err)
return callback(err); return callback(err);
return callback(null, out); return callback(null, items);
}); });
}); });
}; };
@ -296,7 +369,7 @@ TXDB.prototype.writeGenesis = function writeGenesis(callback) {
callback = utils.wrap(callback, unlock); callback = utils.wrap(callback, unlock);
self.db.has('R', function(err, result) { this.db.has('R', function(err, result) {
if (err) if (err)
return callback(err); return callback(err);
@ -304,6 +377,7 @@ TXDB.prototype.writeGenesis = function writeGenesis(callback) {
return callback(); return callback();
hash = new Buffer(self.network.genesis.hash, 'hex'); hash = new Buffer(self.network.genesis.hash, 'hex');
self.db.put('R', hash, callback); self.db.put('R', hash, callback);
}); });
}; };
@ -399,46 +473,6 @@ TXDB.prototype.removeBlock = function removeBlock(block, callback, force) {
}); });
}; };
TXDB.prototype.start = function start() {
assert(!this.current);
this.current = this.db.batch();
return this.current;
};
TXDB.prototype.put = function put(key, value) {
assert(this.current);
this.current.put(key, value);
};
TXDB.prototype.del = function del(key, value) {
assert(this.current);
this.current.del(key);
};
TXDB.prototype.batch = function batch() {
assert(this.current);
return this.current;
};
TXDB.prototype.drop = function drop() {
assert(this.current);
this.current.clear();
this.current = null;
};
TXDB.prototype.commit = function commit(callback) {
var self = this;
assert(this.current);
this.current.write(function(err) {
if (err) {
self.current = null;
return callback(err);
}
self.current = null;
return callback();
});
};
/** /**
* Add a transaction to the database, map addresses * Add a transaction to the database, map addresses
* to wallet IDs, potentially store orphans, resolve * to wallet IDs, potentially store orphans, resolve
@ -456,7 +490,7 @@ TXDB.prototype.add = function add(tx, callback, force) {
callback = utils.wrap(callback, unlock); callback = utils.wrap(callback, unlock);
return this.getInfo(tx, function(err, info) { this.getInfo(tx, function(err, info) {
if (err) if (err)
return callback(err); return callback(err);
@ -469,12 +503,22 @@ TXDB.prototype.add = function add(tx, callback, force) {
self.logger.debug(info.paths); self.logger.debug(info.paths);
return self._add(tx, info, callback); self._add(tx, info, callback);
}); });
}; };
/**
* Retrieve coins for own inputs, remove
* double spenders, and verify inputs.
* @private
* @param {TX} tx
* @param {PathInfo} info
* @param {Function} callback - Returns [Error].
*/
TXDB.prototype._verify = function _verify(tx, info, callback) { TXDB.prototype._verify = function _verify(tx, info, callback) {
var self = this; var self = this;
utils.forEachSerial(tx.inputs, function(input, next, i) { utils.forEachSerial(tx.inputs, function(input, next, i) {
var prevout = input.prevout; var prevout = input.prevout;
var address, paths; var address, paths;
@ -555,12 +599,20 @@ TXDB.prototype._verify = function _verify(tx, info, callback) {
}); });
}; };
TXDB.prototype._resolveOrphans = function _resolveOrphans(tx, i, callback) { /**
* Attempt to resolve orphans for an output.
* @private
* @param {TX} tx
* @param {Number} index
* @param {Function} callback
*/
TXDB.prototype._resolveOrphans = function _resolveOrphans(tx, index, callback) {
var self = this; var self = this;
var batch = this.batch(); var batch = this.batch();
var hash = tx.hash('hex'); var hash = tx.hash('hex');
var output = tx.outputs[i]; var output = tx.outputs[index];
var key = hash + '/' + i; var key = hash + '/' + index;
var coin; var coin;
this._getOrphans(key, function(err, orphans) { this._getOrphans(key, function(err, orphans) {
@ -572,12 +624,12 @@ TXDB.prototype._resolveOrphans = function _resolveOrphans(tx, i, callback) {
batch.del('o/' + key); batch.del('o/' + key);
coin = bcoin.coin.fromTX(tx, i); coin = bcoin.coin.fromTX(tx, index);
// Add input to orphan // Add input to orphan
utils.forEachSerial(orphans, function(pair, next) { utils.forEachSerial(orphans, function(item, next) {
var input = pair[0]; var input = item[0];
var orphan = pair[1]; var orphan = item[1];
// Probably removed by some other means. // Probably removed by some other means.
if (!orphan) if (!orphan)
@ -586,7 +638,7 @@ TXDB.prototype._resolveOrphans = function _resolveOrphans(tx, i, callback) {
orphan.inputs[input.index].coin = coin; orphan.inputs[input.index].coin = coin;
assert(orphan.inputs[input.index].prevout.hash === hash); assert(orphan.inputs[input.index].prevout.hash === hash);
assert(orphan.inputs[input.index].prevout.index === i); assert(orphan.inputs[input.index].prevout.index === index);
// Verify that input script is correct, if not - add // Verify that input script is correct, if not - add
// output to unspent and remove orphan from storage // output to unspent and remove orphan from storage
@ -605,10 +657,19 @@ TXDB.prototype._resolveOrphans = function _resolveOrphans(tx, i, callback) {
}); });
}; };
/**
* Add transaction, runs _confirm (separate batch) and
* verify (separate batch for double spenders).
* @private
* @param {TX} tx
* @param {PathInfo} info
* @param {Function} callback
*/
TXDB.prototype._add = function add(tx, info, callback) { TXDB.prototype._add = function add(tx, info, callback) {
var self = this; var self = this;
var updated = false; var updated = false;
var batch, hash, i, j, unlock, path, paths, id; var batch, hash, i, j, path, paths, id;
if (tx.mutable) if (tx.mutable)
tx = tx.toTX(); tx = tx.toTX();
@ -692,8 +753,10 @@ TXDB.prototype._add = function add(tx, info, callback) {
next(); next();
}, function(err) { }, function(err) {
if (err) if (err) {
self.drop();
return callback(err); return callback(err);
}
// Add unspent outputs or resolve orphans // Add unspent outputs or resolve orphans
utils.forEachSerial(tx.outputs, function(output, next, i) { utils.forEachSerial(tx.outputs, function(output, next, i) {
@ -712,7 +775,7 @@ TXDB.prototype._add = function add(tx, info, callback) {
self._resolveOrphans(tx, i, function(err, orphans) { self._resolveOrphans(tx, i, function(err, orphans) {
if (err) if (err)
return callback(err); return next(err);
if (orphans) if (orphans)
return next(); return next();
@ -736,8 +799,10 @@ TXDB.prototype._add = function add(tx, info, callback) {
next(); next();
}); });
}, function(err) { }, function(err) {
if (err) if (err) {
self.drop();
return callback(err); return callback(err);
}
self.commit(function(err) { self.commit(function(err) {
if (err) if (err)
@ -834,20 +899,19 @@ TXDB.prototype._removeRecursive = function _removeRecursive(tx, callback) {
if (err) if (err)
return next(err); return next(err);
if (!spent)
return next();
// Remove all of the spender's spenders first. // Remove all of the spender's spenders first.
if (spent) { self.getTX(spent.hash, function(err, tx) {
return self.getTX(spent.hash, function(err, tx) { if (err)
if (err) return next(err);
return callback(err);
if (!tx) if (!tx)
return callback(new Error('Could not find spender.')); return next(new Error('Could not find spender.'));
return self._removeRecursive(tx, next); self._removeRecursive(tx, next);
}); });
}
next();
}); });
}, function(err) { }, function(err) {
if (err) if (err)
@ -856,14 +920,16 @@ TXDB.prototype._removeRecursive = function _removeRecursive(tx, callback) {
self.start(); self.start();
// Remove the spender. // Remove the spender.
self._lazyRemove(tx, function(err, res1, res2) { self._lazyRemove(tx, function(err, result, info) {
if (err) if (err) {
self.drop();
return callback(err); return callback(err);
}
self.commit(function(err) { self.commit(function(err) {
if (err) if (err)
return callback(err); return callback(err);
callback(null, res1, res2); return callback(null, result, info);
}); });
}); });
}); });
@ -880,7 +946,8 @@ TXDB.prototype.isDoubleSpend = function isDoubleSpend(tx, callback) {
var self = this; var self = this;
utils.everySerial(tx.inputs, function(input, next) { utils.everySerial(tx.inputs, function(input, next) {
self.isSpent(input.prevout.hash, input.prevout.index, function(err, spent) { var prevout = input.prevout;
self.isSpent(prevout.hash, prevout.index, function(err, spent) {
if (err) if (err)
return next(err); return next(err);
return next(null, !spent); return next(null, !spent);
@ -901,7 +968,7 @@ TXDB.prototype.isDoubleSpend = function isDoubleSpend(tx, callback) {
TXDB.prototype.isSpent = function isSpent(hash, index, callback) { TXDB.prototype.isSpent = function isSpent(hash, index, callback) {
var key = 's/' + hash + '/' + index; var key = 's/' + hash + '/' + index;
return this.db.fetch(key, function(data) { this.db.fetch(key, function(data) {
return bcoin.outpoint.fromRaw(data); return bcoin.outpoint.fromRaw(data);
}, callback); }, callback);
}; };
@ -918,9 +985,8 @@ TXDB.prototype.isSpent = function isSpent(hash, index, callback) {
TXDB.prototype._confirm = function _confirm(tx, info, callback) { TXDB.prototype._confirm = function _confirm(tx, info, callback) {
var self = this; var self = this;
var hash, batch, unlock, i, id; var hash = tx.hash('hex');
var batch, i, id;
hash = tx.hash('hex');
this.getTX(hash, function(err, existing) { this.getTX(hash, function(err, existing) {
if (err) if (err)
@ -982,8 +1048,10 @@ TXDB.prototype._confirm = function _confirm(tx, info, callback) {
next(); next();
}); });
}, function(err) { }, function(err) {
if (err) if (err) {
self.drop();
return callback(err); return callback(err);
}
self.emit('confirmed', tx, info); self.emit('confirmed', tx, info);
self.emit('tx', tx, info); self.emit('tx', tx, info);
@ -1005,40 +1073,18 @@ TXDB.prototype._confirm = function _confirm(tx, info, callback) {
TXDB.prototype.remove = function remove(hash, callback, force) { TXDB.prototype.remove = function remove(hash, callback, force) {
var self = this; var self = this;
var unlock = this._lock(remove, [hash, callback], force);
if (hash.hash) if (!unlock)
hash = hash.hash('hex'); return;
this.getTX(hash, function(err, tx) { callback = utils.wrap(callback, unlock);
this._removeRecursive(hash, function(err, result, info) {
if (err) if (err)
return callback(err); return callback(err);
if (!tx) return callback(null, !!result, info);
return callback(null, true);
assert(tx.hash('hex') === hash);
return self.getInfo(tx, function(err, info) {
if (err)
return callback(err);
if (!info)
return callback(null, false);
self.start();
return self._remove(tx, info, function(err, res1, res2) {
if (err) {
self.drop();
return callback(err);
}
self.commit(function(err) {
if (err)
return callback(err);
callback(null, res1, res2);
});
});
});
}); });
}; };
@ -1059,7 +1105,7 @@ TXDB.prototype._lazyRemove = function lazyRemove(tx, callback) {
if (!info) if (!info)
return callback(null, false); return callback(null, false);
return self._remove(tx, info, callback); self._remove(tx, info, callback);
}); });
}; };
@ -1073,12 +1119,9 @@ TXDB.prototype._lazyRemove = function lazyRemove(tx, callback) {
TXDB.prototype._remove = function remove(tx, info, callback) { TXDB.prototype._remove = function remove(tx, info, callback) {
var self = this; var self = this;
var unlock, hash, batch, i, j, path, id; var hash = tx.hash('hex');
var key, paths, address, input, output, coin; var batch = this.batch();
var i, j, path, id, key, paths, address, input, output, coin;
hash = tx.hash('hex');
batch = this.batch();
batch.del('t/' + hash); batch.del('t/' + hash);
@ -1190,9 +1233,7 @@ TXDB.prototype.unconfirm = function unconfirm(hash, callback, force) {
if (!tx) if (!tx)
return callback(null, true); return callback(null, true);
assert(tx.hash('hex') === hash); self.getInfo(tx, function(err, info) {
return self.getInfo(tx, function(err, info) {
if (err) if (err)
return callback(err); return callback(err);
@ -1201,7 +1242,7 @@ TXDB.prototype.unconfirm = function unconfirm(hash, callback, force) {
self.start(); self.start();
return self._unconfirm(tx, info, function(err, res1, res2) { self._unconfirm(tx, info, function(err, res1, res2) {
if (err) { if (err) {
self.drop(); self.drop();
return callback(err); return callback(err);
@ -1225,11 +1266,10 @@ TXDB.prototype.unconfirm = function unconfirm(hash, callback, force) {
TXDB.prototype._unconfirm = function unconfirm(tx, info, callback, force) { TXDB.prototype._unconfirm = function unconfirm(tx, info, callback, force) {
var self = this; var self = this;
var batch, unlock, hash, height, i, id; var batch = this.batch();
var hash = tx.hash('hex');
batch = this.batch(); var height = tx.height;
hash = tx.hash('hex'); var i, id;
height = tx.height;
if (height !== -1) if (height !== -1)
return callback(null, false, info); return callback(null, false, info);
@ -1453,7 +1493,7 @@ TXDB.prototype.getRange = function getLast(id, options, callback) {
id = null; id = null;
} }
return this.getRangeHashes(id, options, function(err, hashes) { this.getRangeHashes(id, options, function(err, hashes) {
if (err) if (err)
return callback(err); return callback(err);
@ -1492,7 +1532,7 @@ TXDB.prototype.getLast = function getLast(id, limit, callback) {
id = null; id = null;
} }
return this.getRange(id, { this.getRange(id, {
start: 0, start: 0,
end: 0xffffffff, end: 0xffffffff,
reverse: true, reverse: true,
@ -1515,7 +1555,7 @@ TXDB.prototype.getHistory = function getHistory(id, callback) {
id = null; id = null;
} }
return this.getHistoryHashes(id, function(err, hashes) { this.getHistoryHashes(id, function(err, hashes) {
if (err) if (err)
return callback(err); return callback(err);
@ -1554,7 +1594,7 @@ TXDB.prototype.getLastTime = function getLastTime(id, callback) {
id = null; id = null;
} }
return this.getHistory(id, function(err, txs) { this.getHistory(id, function(err, txs) {
if (err) if (err)
return callback(err); return callback(err);
@ -1590,7 +1630,7 @@ TXDB.prototype.getUnconfirmed = function getUnconfirmed(id, callback) {
id = null; id = null;
} }
return this.getUnconfirmedHashes(id, function(err, hashes) { this.getUnconfirmedHashes(id, function(err, hashes) {
if (err) if (err)
return callback(err); return callback(err);
@ -1630,7 +1670,7 @@ TXDB.prototype.getCoins = function getCoins(id, callback) {
id = null; id = null;
} }
return this.getCoinHashes(id, function(err, hashes) { this.getCoinHashes(id, function(err, hashes) {
if (err) if (err)
return callback(err); return callback(err);
@ -1707,10 +1747,12 @@ TXDB.prototype.fillCoins = function fillCoins(tx, callback) {
} }
utils.forEachSerial(tx.inputs, function(input, next) { utils.forEachSerial(tx.inputs, function(input, next) {
var prevout = input.prevout;
if (input.coin) if (input.coin)
return next(); return next();
self.getCoin(input.prevout.hash, input.prevout.index, function(err, coin) { self.getCoin(prevout.hash, prevout.index, function(err, coin) {
if (err) if (err)
return callback(err); return callback(err);
@ -1771,7 +1813,7 @@ TXDB.prototype.toDetails = function toDetails(id, tx, callback) {
if (Array.isArray(tx)) { if (Array.isArray(tx)) {
out = []; out = [];
utils.forEachSerial(tx, function(tx, next) { return utils.forEachSerial(tx, function(tx, next) {
self.toDetails(tx, function(err, details) { self.toDetails(tx, function(err, details) {
if (err) if (err)
return next(err); return next(err);
@ -1812,7 +1854,7 @@ TXDB.prototype.toDetails = function toDetails(id, tx, callback) {
*/ */
TXDB.prototype.hasTX = function hasTX(hash, callback) { TXDB.prototype.hasTX = function hasTX(hash, callback) {
return this.db.has('t/' + hash, callback); this.db.has('t/' + hash, callback);
}; };
/** /**
@ -1859,7 +1901,7 @@ TXDB.prototype.hasCoin = function hasCoin(hash, index, callback) {
if (this.coinCache.has(key)) if (this.coinCache.has(key))
return callback(null, true); return callback(null, true);
return this.db.has('c/' + key, callback); this.db.has('c/' + key, callback);
}; };
/** /**
@ -1891,7 +1933,7 @@ TXDB.prototype.getBalance = function getBalance(id, callback) {
confirmed += value; confirmed += value;
} }
return this.getCoinHashes(id, function(err, hashes) { this.getCoinHashes(id, function(err, hashes) {
if (err) if (err)
return callback(err); return callback(err);
@ -1965,23 +2007,37 @@ TXDB.prototype.zap = function zap(id, age, callback, force) {
if (!utils.isNumber(age)) if (!utils.isNumber(age))
return callback(new Error('Age must be a number.')); return callback(new Error('Age must be a number.'));
return this.getRange(id, { this.getRange(id, {
start: 0, start: 0,
end: bcoin.now() - age end: bcoin.now() - age
}, function(err, txs) { }, function(err, txs) {
if (err) if (err)
return callback(err); return callback(err);
self.fillHistory(txs, function(err) { utils.forEachSerial(txs, function(tx, next) {
if (err) if (tx.ts !== 0)
return callback(err); return next();
self.remove(tx.hash('hex'), next, true);
}, callback);
});
};
utils.forEachSerial(txs, function(tx, next) { /**
if (tx.ts !== 0) * Abandon transaction.
return next(); * @param {Hash} hash
self.remove(tx.hash('hex'), next, true); * @param {Function} callback
}, callback); */
});
TXDB.prototype.abandon = function abandon(hash, callback, force) {
var self = this;
this.db.has('p/' + hash, function(err, result) {
if (err)
return callback(err);
if (!result)
return callback(new Error('TX not found.'));
self.remove(hash, callback, force);
}); });
}; };