From 72c2d25efc760d777634ae2fa0f6e3b39e06a489 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 12 May 2016 15:59:05 -0700 Subject: [PATCH] entries. --- lib/bcoin/mempool.js | 212 ++++++++++++++++++++++++++++--------------- 1 file changed, 139 insertions(+), 73 deletions(-) diff --git a/lib/bcoin/mempool.js b/lib/bcoin/mempool.js index 3e7fbb7f..1069dadb 100644 --- a/lib/bcoin/mempool.js +++ b/lib/bcoin/mempool.js @@ -141,7 +141,7 @@ Mempool.prototype._init = function _init() { self.db = bcoin.ldb(options); // Use the txdb object for its get methods. - self.tx = new bcoin.txdb(self.db); + self.tx = new TXDB(self.db); self.db.open(function(err) { if (err) { @@ -167,6 +167,59 @@ Mempool.prototype._init = function _init() { }); }; +function TXDB(db, options) { + bcoin.txdb.call(this, db, options); +} + +utils.inherits(TXDB, bcoin.txdb); + +TXDB.prototype.getTX = function getTX(hash, callback) { + return this.db.get('t/' + hash, function(err, entry) { + if (err && err.type !== 'NotFoundError') + return callback(err); + + if (!entry) + return callback(); + + try { + entry = MempoolEntry.fromRaw(entry); + } catch (e) { + return callback(e); + } + + return callback(null, entry.tx, entry); + }); +}; + +TXDB.prototype.getRangeEntries = function getRangeEntries(options, callback) { + var self = this; + var entries = []; + + return this.getRangeHashes(options, function(err, hashes) { + if (err) + return callback(err); + + utils.forEachSerial(hashes, function(hash, next) { + self.getTX(hash, function(err, tx, entry) { + if (err) + return callback(err); + + if (!entry) + return next(); + + entries.push(entry); + + next(); + }); + }, function(err) { + if (err) + return callback(err); + + return callback(null, entries); + }); + }); +}; + /** * Tally up total memory usage from database. * @param {Function} callback - Returns [Error, Number]. @@ -226,6 +279,8 @@ Mempool.prototype.addBlock = function addBlock(block, callback, force) { var self = this; var txs = []; var unlock = this._lock(addBlock, [block, callback], force); + var entry; + if (!unlock) return; @@ -234,7 +289,7 @@ Mempool.prototype.addBlock = function addBlock(block, callback, force) { // We add the txs we haven't seen to // the mempool first to potentially // resolve orphans. - utils.forEachSerial(block.txs, function(tx, next) { + utils.forEachSerial(block.txs, function(entry, next) { var hash, copy; if (!self.chain.isFull()) @@ -256,10 +311,9 @@ Mempool.prototype.addBlock = function addBlock(block, callback, force) { if (err) return next(err); - copy = tx.clone(); - copy.unsetBlock(); + entry = MempoolEntry.fromTX(tx, block.height); - self.addUnchecked(tx, next, true); + self.addUnchecked(entry, next, true); }); }); }, function(err) { @@ -273,20 +327,14 @@ Mempool.prototype.addBlock = function addBlock(block, callback, force) { if (tx.isCoinbase()) return next(); - self.getTX(hash, function(err, existing) { + self.getEntry(hash, function(err, entry) { if (err) return next(err); - if (!existing) + if (!entry) return self.removeOrphan(hash, next); - copy = tx.clone(); - copy.ts = existing.ts; - copy.block = existing.block; - copy.height = existing.height; - copy.ps = existing.ps; - - self.removeUnchecked(copy, false, function(err) { + self.removeUnchecked(entry, false, function(err) { if (err) return next(err); @@ -317,6 +365,7 @@ Mempool.prototype.addBlock = function addBlock(block, callback, force) { Mempool.prototype.removeBlock = function removeBlock(block, callback, force) { var self = this; var unlock = this._lock(removeBlock, [block, callback], force); + var entry; if (!unlock) return; @@ -324,8 +373,6 @@ Mempool.prototype.removeBlock = function removeBlock(block, callback, force) { callback = utils.wrap(callback, unlock); utils.forEachSerial(block.txs, function(tx, next) { - var copy; - if (tx.isCoinbase()) return next(); @@ -336,13 +383,9 @@ Mempool.prototype.removeBlock = function removeBlock(block, callback, force) { if (result) return next(); - copy = tx.clone(); - copy.ts = 0; - copy.block = null; - copy.height = -1; - copy.ps = utils.now(); + entry = MempoolEntry.fromTX(tx, block.height); - self.addUnchecked(copy, function(err) { + self.addUnchecked(entry, function(err) { if (err) return next(err); @@ -366,22 +409,22 @@ Mempool.prototype.limitMempoolSize = function limitMempoolSize(callback) { if (this.getSize() <= this.maxSize) return callback(null, true); - this.getRange({ + this.tx.getRangeEntries({ start: 0, end: utils.now() - constants.mempool.MEMPOOL_EXPIRY - }, function(err, txs) { + }, function(err, entries) { if (err) return callback(err); - utils.forEachSerial(function(tx, next) { + utils.forEachSerial(function(entry, next) { if (self.getSize() <= self.maxSize) return next(); - self.removeUnchecked(tx, true, function(err) { + self.removeUnchecked(entry, true, function(err) { if (err) return next(err); - rate = tx.getFee().muln(1000).divn(tx.getVirtualSize()).toNumber(); + rate = entry.fees.muln(1000).divn(entry.size).toNumber(); rate += self.minReasonableFee; if (rate > self.minFeeRate) { @@ -435,6 +478,25 @@ Mempool.prototype.getTX = function getTX(hash, callback) { return this.tx.getTX(hash, callback); }; +/** + * Retrieve a transaction from the mempool. + * Note that this will not be filled with coins. + * @param {TX|Hash} hash + * @param {Function} callback - Returns [Error, {@link TX}]. + */ + +Mempool.prototype.getEntry = function getEntry(hash, callback) { + return this.tx.getTX(hash, function(err, tx, entry) { + if (err) + return callback(err); + + if (!entry) + return callback(); + + return callback(null, entry); + }); +}; + /** * Retrieve a coin from the mempool (unspents only). * @param {Hash} hash @@ -561,7 +623,7 @@ Mempool.prototype.addTX = function addTX(tx, callback, force) { var lockFlags = constants.flags.STANDARD_LOCKTIME_FLAGS; var hash = tx.hash('hex'); var ret = {}; - var now; + var now, entry; var unlock = this._lock(addTX, [tx, callback], force); if (!unlock) @@ -650,7 +712,9 @@ Mempool.prototype.addTX = function addTX(tx, callback, force) { if (!tx.hasCoins()) return self.storeOrphan(tx, callback); - self.verify(tx, function(err) { + entry = MempoolEntry.fromTX(tx, self.chain.height); + + self.verify(entry, function(err) { if (err) return callback(err); @@ -665,7 +729,7 @@ Mempool.prototype.addTX = function addTX(tx, callback, force) { 0)); } - self.addUnchecked(tx, callback, true); + self.addUnchecked(entry, callback, true); }); }); }); @@ -685,33 +749,34 @@ Mempool.prototype.addTX = function addTX(tx, callback, force) { * @param {Function} callback - Returns [{@link VerifyError}]. */ -Mempool.prototype.addUnchecked = function addUnchecked(tx, callback, force) { +Mempool.prototype.addUnchecked = function addUnchecked(entry, callback, force) { var self = this; - var unlock = this._lock(addUnchecked, [tx, callback], force); + var unlock = this._lock(addUnchecked, [entry, callback], force); if (!unlock) return; callback = utils.wrap(callback, unlock); - this._addUnchecked(tx, function(err) { + this._addUnchecked(entry, function(err) { if (err) return callback(err); - self.spent += tx.inputs.length; - self.size += self.memUsage(tx); + self.spent += entry.tx.inputs.length; + self.size += self.memUsage(entry.tx); self.total++; - self.emit('tx', tx); - self.emit('add tx', tx); + self.emit('tx', entry.tx); + self.emit('add tx', entry.tx); - bcoin.debug('Added tx %s to the mempool.', tx.rhash); + bcoin.debug('Added tx %s to the mempool.', entry.tx.rhash); - self.resolveOrphans(tx, function(err, resolved) { + self.resolveOrphans(entry.tx, function(err, resolved) { if (err) return callback(err); utils.forEachSerial(resolved, function(tx, next) { - self.verify(tx, function(err) { + entry = MempoolEntry.fromTX(tx, self.chain.height); + self.verify(entry, function(err) { if (err) { if (err.type === 'VerifyError') { bcoin.debug('Could not resolve orphan %s: %s.', @@ -722,12 +787,12 @@ Mempool.prototype.addUnchecked = function addUnchecked(tx, callback, force) { self.emit('error', err); return next(); } - self.addUnchecked(tx, function(err) { + self.addUnchecked(entry, function(err) { if (err) { self.emit('error', err); return next(); } - bcoin.debug('Resolved orphan %s in mempool.', tx.rhash); + bcoin.debug('Resolved orphan %s in mempool.', entry.tx.rhash); next(); }, true); }); @@ -743,33 +808,33 @@ Mempool.prototype.addUnchecked = function addUnchecked(tx, callback, force) { * @param {Function} callback */ -Mempool.prototype.removeUnchecked = function removeUnchecked(tx, limit, callback, force) { +Mempool.prototype.removeUnchecked = function removeUnchecked(entry, limit, callback, force) { var self = this; var rate; - var unlock = this._lock(removeUnchecked, [tx, limit, callback], force); + var unlock = this._lock(removeUnchecked, [entry, limit, callback], force); if (!unlock) return; callback = utils.wrap(callback, unlock); - this.fillAllHistory(tx, function(err, tx) { + this.fillAllHistory(entry.tx, function(err, tx) { if (err) return callback(err); - self.removeOrphan(tx, function(err) { + self.removeOrphan(entry.tx, function(err) { if (err) return callback(err); - self._removeUnchecked(tx, limit, function(err) { + self._removeUnchecked(entry, limit, function(err) { if (err) return callback(err); - self.spent -= tx.inputs.length; - self.size -= self.memUsage(tx); + self.spent -= entry.tx.inputs.length; + self.size -= self.memUsage(entry.tx); self.total--; - self.emit('remove tx', tx); + self.emit('remove tx', entry.tx); return callback(); }); @@ -820,12 +885,13 @@ Mempool.prototype.getMinRate = function getMinRate() { * @param {Function} callback - Returns [{@link VerifyError}]. */ -Mempool.prototype.verify = function verify(tx, callback) { +Mempool.prototype.verify = function verify(entry, callback) { var self = this; var height = this.chain.height + 1; var lockFlags = constants.flags.STANDARD_LOCKTIME_FLAGS; var flags = constants.flags.STANDARD_VERIFY_FLAGS; var mandatory = constants.flags.MANDATORY_VERIFY_FLAGS; + var tx = entry.tx; var ret = {}; var fee, modFee, now, size, rejectFee, minRelayFee; @@ -857,11 +923,9 @@ Mempool.prototype.verify = function verify(tx, callback) { 0)); } - // In reality we would apply deltas - // to the modified fee (only for mining). fee = tx.getFee(); - modFee = fee; - size = tx.getVirtualSize(); + modFee = entry.fees; + size = entry.size; rejectFee = tx.getMinFee(size, self.getMinRate()); minRelayFee = tx.getMinFee(size, self.minRelayFee); @@ -873,7 +937,7 @@ Mempool.prototype.verify = function verify(tx, callback) { } if (self.relayPriority && modFee.cmp(minRelayFee) < 0) { - if (!tx.isFree(height, size)) { + if (!entry.isFree(height)) { return callback(new VerifyError(tx, 'insufficientfee', 'insufficient priority', @@ -1403,15 +1467,16 @@ Mempool.prototype.getConfidence = function getConfidence(hash, callback) { * @param {Function} callback */ -Mempool.prototype._addUnchecked = function _addUnchecked(tx, callback) { +Mempool.prototype._addUnchecked = function _addUnchecked(entry, callback) { var self = this; + var tx = entry.tx; var hash = tx.hash('hex'); var i, addresses, address, input, output, key, coin, batch; batch = this.db.batch(); batch.put('t/' + hash, tx.toExtended()); - batch.put('m/' + pad32(tx.ps) + '/' + hash, DUMMY); + batch.put('m/' + pad32(entry.ts) + '/' + hash, DUMMY); if (this.options.indexAddress) { addresses = tx.getAddresses(); @@ -1469,22 +1534,23 @@ Mempool.prototype._addUnchecked = function _addUnchecked(tx, callback) { Mempool.prototype._removeUnchecked = function _removeUnchecked(hash, limit, callback) { var self = this; - var batch, i, addresses, output; + var batch, i, addresses, output, tx; - if (hash.hash) - hash = hash.hash('hex'); + if (hash.tx) + hash = hash.tx.hash('hex'); - this.getTX(hash, function(err, tx) { + this.getEntry(hash, function(err, entry) { if (err) return callback(err); - if (!tx) + if (!entry) return callback(); + tx = entry.tx; batch = self.db.batch(); batch.del('t/' + hash); - batch.del('m/' + pad32(tx.ps) + '/' + hash); + batch.del('m/' + pad32(entry.ts) + '/' + hash); if (self.options.indexAddress) { addresses = tx.getAddresses(); @@ -1617,7 +1683,7 @@ Mempool.prototype.getSize = function getSize() { + this.size; }; -function MempoolTX(options) { +function MempoolEntry(options) { this.tx = options.tx; this.height = options.height; this.priority = options.priority; @@ -1629,10 +1695,10 @@ function MempoolTX(options) { this.fees = options.fees; } -MempoolTX.fromTX = function fromTX(tx, height) { +MempoolEntry.fromTX = function fromTX(tx, height) { var data = tx.getPriority(height); - return new MempoolTX({ + return new MempoolEntry({ tx: tx, height: height, priority: data.priority, @@ -1644,7 +1710,7 @@ MempoolTX.fromTX = function fromTX(tx, height) { }); }; -MempoolTX.prototype.toRaw = function toRaw() { +MempoolEntry.prototype.toRaw = function toRaw() { var p = new BufferWriter(); bcoin.protocol.framer.tx(this.tx, p); p.writeU32(this.height); @@ -1657,10 +1723,10 @@ MempoolTX.prototype.toRaw = function toRaw() { return p.render(); }; -MempoolTX.fromRaw = function fromRaw(data, saveCoins) { +MempoolEntry.fromRaw = function fromRaw(data, saveCoins) { var p = new BufferReader(data); - return new MempoolTX({ - tx: bcoin.protocol.parser.parseTX(p), + return new MempoolEntry({ + tx: bcoin.tx.fromRaw(p), height: p.readU32(), priority: p.readVarint(true), chainValue: p.readVarint(true), @@ -1671,7 +1737,7 @@ MempoolTX.fromRaw = function fromRaw(data, saveCoins) { }); }; -MempoolTX.prototype.getPriority = function getPriority(height) { +MempoolEntry.prototype.getPriority = function getPriority(height) { var heightDelta = Math.max(0, height - this.height); var modSize = this.tx.getModifiedSize(this.size); var deltaPriority = new bn(heightDelta).mul(this.chainValue).divn(modSize); @@ -1681,7 +1747,7 @@ MempoolTX.prototype.getPriority = function getPriority(height) { return result; }; -MempoolTX.prototype.isFree = function isFree(height) { +MempoolEntry.prototype.isFree = function isFree(height) { var priority = this.getPriority(height); return priority.cmp(constants.tx.FREE_THRESHOLD) > 0; };