mempool: refactor descendants handling.
This commit is contained in:
parent
8977e99906
commit
46a646aebd
@ -127,7 +127,7 @@ Mempool.prototype._open = co(function* open() {
|
|||||||
for (i = 0; i < entries.length; i++) {
|
for (i = 0; i < entries.length; i++) {
|
||||||
entry = entries[i];
|
entry = entries[i];
|
||||||
|
|
||||||
this.updateAncestors(entry);
|
this.updateAncestors(entry, addFee);
|
||||||
|
|
||||||
if (this.options.indexAddress) {
|
if (this.options.indexAddress) {
|
||||||
view = yield this.getCoinView(entry.tx);
|
view = yield this.getCoinView(entry.tx);
|
||||||
@ -385,7 +385,7 @@ Mempool.prototype.limitSize = function limitSize(entryHash) {
|
|||||||
if (!trimmed && hash === entryHash)
|
if (!trimmed && hash === entryHash)
|
||||||
trimmed = true;
|
trimmed = true;
|
||||||
|
|
||||||
this.removeEntry(entry, true);
|
this.evictEntry(entry);
|
||||||
|
|
||||||
if (this.getSize() <= this.options.maxSize)
|
if (this.getSize() <= this.options.maxSize)
|
||||||
return trimmed;
|
return trimmed;
|
||||||
@ -403,7 +403,7 @@ Mempool.prototype.limitSize = function limitSize(entryHash) {
|
|||||||
if (!trimmed && hash === entryHash)
|
if (!trimmed && hash === entryHash)
|
||||||
trimmed = true;
|
trimmed = true;
|
||||||
|
|
||||||
this.removeEntry(entry, true);
|
this.evictEntry(entry);
|
||||||
|
|
||||||
if (this.getSize() <= this.options.maxSize)
|
if (this.getSize() <= this.options.maxSize)
|
||||||
return trimmed;
|
return trimmed;
|
||||||
@ -957,7 +957,7 @@ Mempool.prototype.verify = co(function* verify(entry, view) {
|
|||||||
throw new VerifyError(tx, 'highfee', 'absurdly-high-fee', 0);
|
throw new VerifyError(tx, 'highfee', 'absurdly-high-fee', 0);
|
||||||
|
|
||||||
// Why do we have this here? Nested transactions are cool.
|
// Why do we have this here? Nested transactions are cool.
|
||||||
if (this.updateAncestors(entry) > this.options.maxAncestors) {
|
if (this.countAncestors(entry) + 1 > this.options.maxAncestors) {
|
||||||
throw new VerifyError(tx,
|
throw new VerifyError(tx,
|
||||||
'nonstandard',
|
'nonstandard',
|
||||||
'too-long-mempool-chain',
|
'too-long-mempool-chain',
|
||||||
@ -1076,6 +1076,8 @@ Mempool.prototype.addEntry = co(function* addEntry(entry, view) {
|
|||||||
|
|
||||||
this.trackEntry(entry, view);
|
this.trackEntry(entry, view);
|
||||||
|
|
||||||
|
this.updateAncestors(entry, addFee);
|
||||||
|
|
||||||
this.emit('tx', tx, view);
|
this.emit('tx', tx, view);
|
||||||
this.emit('add entry', entry);
|
this.emit('add entry', entry);
|
||||||
|
|
||||||
@ -1090,27 +1092,16 @@ Mempool.prototype.addEntry = co(function* addEntry(entry, view) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a transaction from the mempool. Generally
|
* Remove a transaction from the mempool.
|
||||||
* only called when a new block is added to the main chain.
|
* Generally only called when a new block
|
||||||
|
* is added to the main chain.
|
||||||
* @param {MempoolEntry} entry
|
* @param {MempoolEntry} entry
|
||||||
* @param {Boolean} limit
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Mempool.prototype.removeEntry = function removeEntry(entry, limit) {
|
Mempool.prototype.removeEntry = function removeEntry(entry) {
|
||||||
var tx = entry.tx;
|
var tx = entry.tx;
|
||||||
var hash = tx.hash('hex');
|
var hash = tx.hash('hex');
|
||||||
|
|
||||||
// We do not remove spenders if this is
|
|
||||||
// being removed for a block. The spenders
|
|
||||||
// are still spending valid coins (which
|
|
||||||
// now exist on the blockchain).
|
|
||||||
if (limit) {
|
|
||||||
this.removeSpenders(entry);
|
|
||||||
this.logger.debug('Evicting %s from the mempool.', tx.txid());
|
|
||||||
} else {
|
|
||||||
this.logger.spam('Removing block tx %s from mempool.', tx.txid());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.untrackEntry(entry);
|
this.untrackEntry(entry);
|
||||||
|
|
||||||
if (this.fees)
|
if (this.fees)
|
||||||
@ -1121,6 +1112,45 @@ Mempool.prototype.removeEntry = function removeEntry(entry, limit) {
|
|||||||
this.emit('remove entry', entry);
|
this.emit('remove entry', entry);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evict a transaction from the mempool.
|
||||||
|
* Recursively remove its spenders.
|
||||||
|
* @param {MempoolEntry} entry
|
||||||
|
*/
|
||||||
|
|
||||||
|
Mempool.prototype.evictEntry = function evictEntry(entry) {
|
||||||
|
var tx = entry.tx;
|
||||||
|
|
||||||
|
this.logger.debug('Evicting %s from the mempool.', tx.txid());
|
||||||
|
|
||||||
|
this.removeSpenders(entry);
|
||||||
|
this.updateAncestors(entry, removeFee);
|
||||||
|
|
||||||
|
this.removeEntry(entry);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively remove spenders of a transaction.
|
||||||
|
* @private
|
||||||
|
* @param {MempoolEntry} entry
|
||||||
|
*/
|
||||||
|
|
||||||
|
Mempool.prototype.removeSpenders = function removeSpenders(entry) {
|
||||||
|
var tx = entry.tx;
|
||||||
|
var hash = tx.hash('hex');
|
||||||
|
var i, spender;
|
||||||
|
|
||||||
|
for (i = 0; i < tx.outputs.length; i++) {
|
||||||
|
spender = this.getSpent(hash, i);
|
||||||
|
|
||||||
|
if (!spender)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
this.removeSpenders(spender);
|
||||||
|
this.removeEntry(spender);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Count the highest number of
|
* Count the highest number of
|
||||||
* ancestors a transaction may have.
|
* ancestors a transaction may have.
|
||||||
@ -1129,7 +1159,7 @@ Mempool.prototype.removeEntry = function removeEntry(entry, limit) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
Mempool.prototype.countAncestors = function countAncestors(entry) {
|
Mempool.prototype.countAncestors = function countAncestors(entry) {
|
||||||
return this._countAncestors(entry, 0, {}, entry, false);
|
return this._countAncestors(entry, 0, {}, entry, nop);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1137,11 +1167,12 @@ Mempool.prototype.countAncestors = function countAncestors(entry) {
|
|||||||
* ancestors a transaction may have.
|
* ancestors a transaction may have.
|
||||||
* Update descendant fees and size.
|
* Update descendant fees and size.
|
||||||
* @param {MempoolEntry} entry
|
* @param {MempoolEntry} entry
|
||||||
|
* @param {Function} map
|
||||||
* @returns {Number}
|
* @returns {Number}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Mempool.prototype.updateAncestors = function updateAncestors(entry) {
|
Mempool.prototype.updateAncestors = function updateAncestors(entry, map) {
|
||||||
return this._countAncestors(entry, 0, {}, entry, true);
|
return this._countAncestors(entry, 0, {}, entry, map);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1151,20 +1182,20 @@ Mempool.prototype.updateAncestors = function updateAncestors(entry) {
|
|||||||
* @param {Number} count
|
* @param {Number} count
|
||||||
* @param {Object} set
|
* @param {Object} set
|
||||||
* @param {MempoolEntry} child
|
* @param {MempoolEntry} child
|
||||||
* @param {Boolean} update
|
* @param {Function} map
|
||||||
* @returns {Number}
|
* @returns {Number}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Mempool.prototype._countAncestors = function countAncestors(entry, count, set, child, update) {
|
Mempool.prototype._countAncestors = function countAncestors(entry, count, set, child, map) {
|
||||||
var tx = entry.tx;
|
var tx = entry.tx;
|
||||||
var i, input, hash, pentry;
|
var i, input, hash, parent;
|
||||||
|
|
||||||
for (i = 0; i < tx.inputs.length; i++) {
|
for (i = 0; i < tx.inputs.length; i++) {
|
||||||
input = tx.inputs[i];
|
input = tx.inputs[i];
|
||||||
hash = input.prevout.hash;
|
hash = input.prevout.hash;
|
||||||
pentry = this.getEntry(hash);
|
parent = this.getEntry(hash);
|
||||||
|
|
||||||
if (!pentry)
|
if (!parent)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (set[hash])
|
if (set[hash])
|
||||||
@ -1173,15 +1204,12 @@ Mempool.prototype._countAncestors = function countAncestors(entry, count, set, c
|
|||||||
set[hash] = true;
|
set[hash] = true;
|
||||||
count += 1;
|
count += 1;
|
||||||
|
|
||||||
if (update) {
|
map(parent, child);
|
||||||
pentry.descFee += child.fee;
|
|
||||||
pentry.descSize += child.size;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count > this.options.maxAncestors)
|
if (count > this.options.maxAncestors)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
count = this._countAncestors(pentry, count, set, child, update);
|
count = this._countAncestors(parent, count, set, child, map);
|
||||||
|
|
||||||
if (count > this.options.maxAncestors)
|
if (count > this.options.maxAncestors)
|
||||||
break;
|
break;
|
||||||
@ -1214,23 +1242,23 @@ Mempool.prototype.countDescendants = function countDescendants(entry) {
|
|||||||
Mempool.prototype._countDescendants = function countDescendants(entry, count, set) {
|
Mempool.prototype._countDescendants = function countDescendants(entry, count, set) {
|
||||||
var tx = entry.tx;
|
var tx = entry.tx;
|
||||||
var hash = tx.hash('hex');
|
var hash = tx.hash('hex');
|
||||||
var i, next, nhash;
|
var i, child, next;
|
||||||
|
|
||||||
for (i = 0; i < tx.outputs.length; i++) {
|
for (i = 0; i < tx.outputs.length; i++) {
|
||||||
next = this.getSpent(hash, i);
|
child = this.getSpent(hash, i);
|
||||||
|
|
||||||
if (!next)
|
if (!child)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
nhash = next.tx.hash('hex');
|
next = child.tx.hash('hex');
|
||||||
|
|
||||||
if (set[nhash])
|
if (set[next])
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
set[nhash] = true;
|
set[next] = true;
|
||||||
count += 1;
|
count += 1;
|
||||||
|
|
||||||
count = this._countDescendants(next, count, set);
|
count = this._countDescendants(child, count, set);
|
||||||
}
|
}
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
@ -1256,23 +1284,23 @@ Mempool.prototype.getAncestors = function getAncestors(tx) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
Mempool.prototype._getAncestors = function getAncestors(tx, entries, set) {
|
Mempool.prototype._getAncestors = function getAncestors(tx, entries, set) {
|
||||||
var i, hash, input, prev;
|
var i, hash, input, parent;
|
||||||
|
|
||||||
for (i = 0; i < tx.inputs.length; i++) {
|
for (i = 0; i < tx.inputs.length; i++) {
|
||||||
input = tx.inputs[i];
|
input = tx.inputs[i];
|
||||||
hash = input.prevout.hash;
|
hash = input.prevout.hash;
|
||||||
prev = this.getTX(hash);
|
parent = this.getTX(hash);
|
||||||
|
|
||||||
if (!prev)
|
if (!parent)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (set[hash])
|
if (set[hash])
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
set[hash] = true;
|
set[hash] = true;
|
||||||
entries.push(prev);
|
entries.push(parent);
|
||||||
|
|
||||||
this._getAncestors(prev, entries, set);
|
this._getAncestors(parent, entries, set);
|
||||||
}
|
}
|
||||||
|
|
||||||
return entries;
|
return entries;
|
||||||
@ -1298,23 +1326,23 @@ Mempool.prototype.getDescendants = function getDescendants(tx) {
|
|||||||
|
|
||||||
Mempool.prototype._getDescendants = function getDescendants(tx, entries, set) {
|
Mempool.prototype._getDescendants = function getDescendants(tx, entries, set) {
|
||||||
var hash = tx.hash('hex');
|
var hash = tx.hash('hex');
|
||||||
var i, next, nhash;
|
var i, child, next;
|
||||||
|
|
||||||
for (i = 0; i < tx.outputs.length; i++) {
|
for (i = 0; i < tx.outputs.length; i++) {
|
||||||
next = this.getSpentTX(hash, i);
|
child = this.getSpentTX(hash, i);
|
||||||
|
|
||||||
if (!next)
|
if (!child)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
nhash = next.hash('hex');
|
next = child.hash('hex');
|
||||||
|
|
||||||
if (set[nhash])
|
if (set[next])
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
set[nhash] = true;
|
set[next] = true;
|
||||||
entries.push(next);
|
entries.push(child);
|
||||||
|
|
||||||
this._getDescendants(next, entries, set);
|
this._getDescendants(child, entries, set);
|
||||||
}
|
}
|
||||||
|
|
||||||
return entries;
|
return entries;
|
||||||
@ -1831,27 +1859,6 @@ Mempool.prototype.unindexEntry = function unindexEntry(entry) {
|
|||||||
this.coinIndex.remove(hash, i);
|
this.coinIndex.remove(hash, i);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively remove spenders of a transaction.
|
|
||||||
* @private
|
|
||||||
* @param {MempoolEntry} entry
|
|
||||||
*/
|
|
||||||
|
|
||||||
Mempool.prototype.removeSpenders = function removeSpenders(entry) {
|
|
||||||
var tx = entry.tx;
|
|
||||||
var hash = tx.hash('hex');
|
|
||||||
var i, spender;
|
|
||||||
|
|
||||||
for (i = 0; i < tx.outputs.length; i++) {
|
|
||||||
spender = this.getSpent(hash, i);
|
|
||||||
|
|
||||||
if (!spender)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
this.removeEntry(spender, true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively remove double spenders
|
* Recursively remove double spenders
|
||||||
* of a mined transaction's outpoints.
|
* of a mined transaction's outpoints.
|
||||||
@ -1874,7 +1881,7 @@ Mempool.prototype.removeDoubleSpends = function removeDoubleSpends(tx) {
|
|||||||
'Removing double spender from mempool: %s.',
|
'Removing double spender from mempool: %s.',
|
||||||
spent.tx.rhash());
|
spent.tx.rhash());
|
||||||
|
|
||||||
this.removeEntry(spent, true);
|
this.evictEntry(spent);
|
||||||
|
|
||||||
this.emit('double spend', spent);
|
this.emit('double spend', spent);
|
||||||
}
|
}
|
||||||
@ -2492,6 +2499,24 @@ MempoolCache.prototype.wipe = co(function* wipe() {
|
|||||||
this.logger.info('Removed %d mempool entries from disk.', keys.length);
|
this.logger.info('Removed %d mempool entries from disk.', keys.length);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helpers
|
||||||
|
*/
|
||||||
|
|
||||||
|
function nop(parent, child) {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addFee(parent, child) {
|
||||||
|
parent.descFee += child.fee;
|
||||||
|
parent.descSize += child.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeFee(parent, child) {
|
||||||
|
parent.descFee -= child.descFee;
|
||||||
|
parent.descSize -= child.descSize;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Expose
|
* Expose
|
||||||
*/
|
*/
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user