This commit is contained in:
Christopher Jeffrey 2016-05-12 15:59:05 -07:00
parent 4ce1ed3046
commit 72c2d25efc
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD

View File

@ -141,7 +141,7 @@ Mempool.prototype._init = function _init() {
self.db = bcoin.ldb(options); self.db = bcoin.ldb(options);
// Use the txdb object for its get methods. // 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) { self.db.open(function(err) {
if (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. * Tally up total memory usage from database.
* @param {Function} callback - Returns [Error, Number]. * @param {Function} callback - Returns [Error, Number].
@ -226,6 +279,8 @@ Mempool.prototype.addBlock = function addBlock(block, callback, force) {
var self = this; var self = this;
var txs = []; var txs = [];
var unlock = this._lock(addBlock, [block, callback], force); var unlock = this._lock(addBlock, [block, callback], force);
var entry;
if (!unlock) if (!unlock)
return; return;
@ -234,7 +289,7 @@ Mempool.prototype.addBlock = function addBlock(block, callback, force) {
// We add the txs we haven't seen to // We add the txs we haven't seen to
// the mempool first to potentially // the mempool first to potentially
// resolve orphans. // resolve orphans.
utils.forEachSerial(block.txs, function(tx, next) { utils.forEachSerial(block.txs, function(entry, next) {
var hash, copy; var hash, copy;
if (!self.chain.isFull()) if (!self.chain.isFull())
@ -256,10 +311,9 @@ Mempool.prototype.addBlock = function addBlock(block, callback, force) {
if (err) if (err)
return next(err); return next(err);
copy = tx.clone(); entry = MempoolEntry.fromTX(tx, block.height);
copy.unsetBlock();
self.addUnchecked(tx, next, true); self.addUnchecked(entry, next, true);
}); });
}); });
}, function(err) { }, function(err) {
@ -273,20 +327,14 @@ Mempool.prototype.addBlock = function addBlock(block, callback, force) {
if (tx.isCoinbase()) if (tx.isCoinbase())
return next(); return next();
self.getTX(hash, function(err, existing) { self.getEntry(hash, function(err, entry) {
if (err) if (err)
return next(err); return next(err);
if (!existing) if (!entry)
return self.removeOrphan(hash, next); return self.removeOrphan(hash, next);
copy = tx.clone(); self.removeUnchecked(entry, false, function(err) {
copy.ts = existing.ts;
copy.block = existing.block;
copy.height = existing.height;
copy.ps = existing.ps;
self.removeUnchecked(copy, false, function(err) {
if (err) if (err)
return next(err); return next(err);
@ -317,6 +365,7 @@ Mempool.prototype.addBlock = function addBlock(block, callback, force) {
Mempool.prototype.removeBlock = function removeBlock(block, callback, force) { Mempool.prototype.removeBlock = function removeBlock(block, callback, force) {
var self = this; var self = this;
var unlock = this._lock(removeBlock, [block, callback], force); var unlock = this._lock(removeBlock, [block, callback], force);
var entry;
if (!unlock) if (!unlock)
return; return;
@ -324,8 +373,6 @@ Mempool.prototype.removeBlock = function removeBlock(block, callback, force) {
callback = utils.wrap(callback, unlock); callback = utils.wrap(callback, unlock);
utils.forEachSerial(block.txs, function(tx, next) { utils.forEachSerial(block.txs, function(tx, next) {
var copy;
if (tx.isCoinbase()) if (tx.isCoinbase())
return next(); return next();
@ -336,13 +383,9 @@ Mempool.prototype.removeBlock = function removeBlock(block, callback, force) {
if (result) if (result)
return next(); return next();
copy = tx.clone(); entry = MempoolEntry.fromTX(tx, block.height);
copy.ts = 0;
copy.block = null;
copy.height = -1;
copy.ps = utils.now();
self.addUnchecked(copy, function(err) { self.addUnchecked(entry, function(err) {
if (err) if (err)
return next(err); return next(err);
@ -366,22 +409,22 @@ Mempool.prototype.limitMempoolSize = function limitMempoolSize(callback) {
if (this.getSize() <= this.maxSize) if (this.getSize() <= this.maxSize)
return callback(null, true); return callback(null, true);
this.getRange({ this.tx.getRangeEntries({
start: 0, start: 0,
end: utils.now() - constants.mempool.MEMPOOL_EXPIRY end: utils.now() - constants.mempool.MEMPOOL_EXPIRY
}, function(err, txs) { }, function(err, entries) {
if (err) if (err)
return callback(err); return callback(err);
utils.forEachSerial(function(tx, next) { utils.forEachSerial(function(entry, next) {
if (self.getSize() <= self.maxSize) if (self.getSize() <= self.maxSize)
return next(); return next();
self.removeUnchecked(tx, true, function(err) { self.removeUnchecked(entry, true, function(err) {
if (err) if (err)
return next(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; rate += self.minReasonableFee;
if (rate > self.minFeeRate) { if (rate > self.minFeeRate) {
@ -435,6 +478,25 @@ Mempool.prototype.getTX = function getTX(hash, callback) {
return this.tx.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). * Retrieve a coin from the mempool (unspents only).
* @param {Hash} hash * @param {Hash} hash
@ -561,7 +623,7 @@ Mempool.prototype.addTX = function addTX(tx, callback, force) {
var lockFlags = constants.flags.STANDARD_LOCKTIME_FLAGS; var lockFlags = constants.flags.STANDARD_LOCKTIME_FLAGS;
var hash = tx.hash('hex'); var hash = tx.hash('hex');
var ret = {}; var ret = {};
var now; var now, entry;
var unlock = this._lock(addTX, [tx, callback], force); var unlock = this._lock(addTX, [tx, callback], force);
if (!unlock) if (!unlock)
@ -650,7 +712,9 @@ Mempool.prototype.addTX = function addTX(tx, callback, force) {
if (!tx.hasCoins()) if (!tx.hasCoins())
return self.storeOrphan(tx, callback); return self.storeOrphan(tx, callback);
self.verify(tx, function(err) { entry = MempoolEntry.fromTX(tx, self.chain.height);
self.verify(entry, function(err) {
if (err) if (err)
return callback(err); return callback(err);
@ -665,7 +729,7 @@ Mempool.prototype.addTX = function addTX(tx, callback, force) {
0)); 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}]. * @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 self = this;
var unlock = this._lock(addUnchecked, [tx, callback], force); var unlock = this._lock(addUnchecked, [entry, callback], force);
if (!unlock) if (!unlock)
return; return;
callback = utils.wrap(callback, unlock); callback = utils.wrap(callback, unlock);
this._addUnchecked(tx, function(err) { this._addUnchecked(entry, function(err) {
if (err) if (err)
return callback(err); return callback(err);
self.spent += tx.inputs.length; self.spent += entry.tx.inputs.length;
self.size += self.memUsage(tx); self.size += self.memUsage(entry.tx);
self.total++; self.total++;
self.emit('tx', tx); self.emit('tx', entry.tx);
self.emit('add tx', 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) if (err)
return callback(err); return callback(err);
utils.forEachSerial(resolved, function(tx, next) { 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) {
if (err.type === 'VerifyError') { if (err.type === 'VerifyError') {
bcoin.debug('Could not resolve orphan %s: %s.', bcoin.debug('Could not resolve orphan %s: %s.',
@ -722,12 +787,12 @@ Mempool.prototype.addUnchecked = function addUnchecked(tx, callback, force) {
self.emit('error', err); self.emit('error', err);
return next(); return next();
} }
self.addUnchecked(tx, function(err) { self.addUnchecked(entry, function(err) {
if (err) { if (err) {
self.emit('error', err); self.emit('error', err);
return next(); return next();
} }
bcoin.debug('Resolved orphan %s in mempool.', tx.rhash); bcoin.debug('Resolved orphan %s in mempool.', entry.tx.rhash);
next(); next();
}, true); }, true);
}); });
@ -743,33 +808,33 @@ Mempool.prototype.addUnchecked = function addUnchecked(tx, callback, force) {
* @param {Function} callback * @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 self = this;
var rate; var rate;
var unlock = this._lock(removeUnchecked, [tx, limit, callback], force); var unlock = this._lock(removeUnchecked, [entry, limit, callback], force);
if (!unlock) if (!unlock)
return; return;
callback = utils.wrap(callback, unlock); callback = utils.wrap(callback, unlock);
this.fillAllHistory(tx, function(err, tx) { this.fillAllHistory(entry.tx, function(err, tx) {
if (err) if (err)
return callback(err); return callback(err);
self.removeOrphan(tx, function(err) { self.removeOrphan(entry.tx, function(err) {
if (err) if (err)
return callback(err); return callback(err);
self._removeUnchecked(tx, limit, function(err) { self._removeUnchecked(entry, limit, function(err) {
if (err) if (err)
return callback(err); return callback(err);
self.spent -= tx.inputs.length; self.spent -= entry.tx.inputs.length;
self.size -= self.memUsage(tx); self.size -= self.memUsage(entry.tx);
self.total--; self.total--;
self.emit('remove tx', tx); self.emit('remove tx', entry.tx);
return callback(); return callback();
}); });
@ -820,12 +885,13 @@ Mempool.prototype.getMinRate = function getMinRate() {
* @param {Function} callback - Returns [{@link VerifyError}]. * @param {Function} callback - Returns [{@link VerifyError}].
*/ */
Mempool.prototype.verify = function verify(tx, callback) { Mempool.prototype.verify = function verify(entry, callback) {
var self = this; var self = this;
var height = this.chain.height + 1; var height = this.chain.height + 1;
var lockFlags = constants.flags.STANDARD_LOCKTIME_FLAGS; var lockFlags = constants.flags.STANDARD_LOCKTIME_FLAGS;
var flags = constants.flags.STANDARD_VERIFY_FLAGS; var flags = constants.flags.STANDARD_VERIFY_FLAGS;
var mandatory = constants.flags.MANDATORY_VERIFY_FLAGS; var mandatory = constants.flags.MANDATORY_VERIFY_FLAGS;
var tx = entry.tx;
var ret = {}; var ret = {};
var fee, modFee, now, size, rejectFee, minRelayFee; var fee, modFee, now, size, rejectFee, minRelayFee;
@ -857,11 +923,9 @@ Mempool.prototype.verify = function verify(tx, callback) {
0)); 0));
} }
// In reality we would apply deltas
// to the modified fee (only for mining).
fee = tx.getFee(); fee = tx.getFee();
modFee = fee; modFee = entry.fees;
size = tx.getVirtualSize(); size = entry.size;
rejectFee = tx.getMinFee(size, self.getMinRate()); rejectFee = tx.getMinFee(size, self.getMinRate());
minRelayFee = tx.getMinFee(size, self.minRelayFee); 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 (self.relayPriority && modFee.cmp(minRelayFee) < 0) {
if (!tx.isFree(height, size)) { if (!entry.isFree(height)) {
return callback(new VerifyError(tx, return callback(new VerifyError(tx,
'insufficientfee', 'insufficientfee',
'insufficient priority', 'insufficient priority',
@ -1403,15 +1467,16 @@ Mempool.prototype.getConfidence = function getConfidence(hash, callback) {
* @param {Function} callback * @param {Function} callback
*/ */
Mempool.prototype._addUnchecked = function _addUnchecked(tx, callback) { Mempool.prototype._addUnchecked = function _addUnchecked(entry, callback) {
var self = this; var self = this;
var tx = entry.tx;
var hash = tx.hash('hex'); var hash = tx.hash('hex');
var i, addresses, address, input, output, key, coin, batch; var i, addresses, address, input, output, key, coin, batch;
batch = this.db.batch(); batch = this.db.batch();
batch.put('t/' + hash, tx.toExtended()); 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) { if (this.options.indexAddress) {
addresses = tx.getAddresses(); addresses = tx.getAddresses();
@ -1469,22 +1534,23 @@ Mempool.prototype._addUnchecked = function _addUnchecked(tx, callback) {
Mempool.prototype._removeUnchecked = function _removeUnchecked(hash, limit, callback) { Mempool.prototype._removeUnchecked = function _removeUnchecked(hash, limit, callback) {
var self = this; var self = this;
var batch, i, addresses, output; var batch, i, addresses, output, tx;
if (hash.hash) if (hash.tx)
hash = hash.hash('hex'); hash = hash.tx.hash('hex');
this.getTX(hash, function(err, tx) { this.getEntry(hash, function(err, entry) {
if (err) if (err)
return callback(err); return callback(err);
if (!tx) if (!entry)
return callback(); return callback();
tx = entry.tx;
batch = self.db.batch(); batch = self.db.batch();
batch.del('t/' + hash); batch.del('t/' + hash);
batch.del('m/' + pad32(tx.ps) + '/' + hash); batch.del('m/' + pad32(entry.ts) + '/' + hash);
if (self.options.indexAddress) { if (self.options.indexAddress) {
addresses = tx.getAddresses(); addresses = tx.getAddresses();
@ -1617,7 +1683,7 @@ Mempool.prototype.getSize = function getSize() {
+ this.size; + this.size;
}; };
function MempoolTX(options) { function MempoolEntry(options) {
this.tx = options.tx; this.tx = options.tx;
this.height = options.height; this.height = options.height;
this.priority = options.priority; this.priority = options.priority;
@ -1629,10 +1695,10 @@ function MempoolTX(options) {
this.fees = options.fees; this.fees = options.fees;
} }
MempoolTX.fromTX = function fromTX(tx, height) { MempoolEntry.fromTX = function fromTX(tx, height) {
var data = tx.getPriority(height); var data = tx.getPriority(height);
return new MempoolTX({ return new MempoolEntry({
tx: tx, tx: tx,
height: height, height: height,
priority: data.priority, 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(); var p = new BufferWriter();
bcoin.protocol.framer.tx(this.tx, p); bcoin.protocol.framer.tx(this.tx, p);
p.writeU32(this.height); p.writeU32(this.height);
@ -1657,10 +1723,10 @@ MempoolTX.prototype.toRaw = function toRaw() {
return p.render(); return p.render();
}; };
MempoolTX.fromRaw = function fromRaw(data, saveCoins) { MempoolEntry.fromRaw = function fromRaw(data, saveCoins) {
var p = new BufferReader(data); var p = new BufferReader(data);
return new MempoolTX({ return new MempoolEntry({
tx: bcoin.protocol.parser.parseTX(p), tx: bcoin.tx.fromRaw(p),
height: p.readU32(), height: p.readU32(),
priority: p.readVarint(true), priority: p.readVarint(true),
chainValue: 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 heightDelta = Math.max(0, height - this.height);
var modSize = this.tx.getModifiedSize(this.size); var modSize = this.tx.getModifiedSize(this.size);
var deltaPriority = new bn(heightDelta).mul(this.chainValue).divn(modSize); var deltaPriority = new bn(heightDelta).mul(this.chainValue).divn(modSize);
@ -1681,7 +1747,7 @@ MempoolTX.prototype.getPriority = function getPriority(height) {
return result; return result;
}; };
MempoolTX.prototype.isFree = function isFree(height) { MempoolEntry.prototype.isFree = function isFree(height) {
var priority = this.getPriority(height); var priority = this.getPriority(height);
return priority.cmp(constants.tx.FREE_THRESHOLD) > 0; return priority.cmp(constants.tx.FREE_THRESHOLD) > 0;
}; };