refactor mempool orphans.
This commit is contained in:
parent
c37a069978
commit
88a5b8a784
@ -247,8 +247,10 @@ Mempool.prototype.addBlock = function addBlock(block, callback, force) {
|
|||||||
if (err)
|
if (err)
|
||||||
return next(err);
|
return next(err);
|
||||||
|
|
||||||
if (!entry)
|
if (!entry) {
|
||||||
return self.removeOrphan(hash, next);
|
self.removeOrphan(hash);
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
self.removeUnchecked(entry, false, function(err) {
|
self.removeUnchecked(entry, false, function(err) {
|
||||||
if (err)
|
if (err)
|
||||||
@ -380,29 +382,21 @@ Mempool.prototype.limitMempoolSize = function limitMempoolSize(entryHash, callba
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Purge orphan transactions from the mempool.
|
* Purge orphan transactions from the mempool.
|
||||||
* @param {Function} callback
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Mempool.prototype.limitOrphans = function limitOrphans(callback) {
|
Mempool.prototype.limitOrphans = function limitOrphans() {
|
||||||
var self = this;
|
|
||||||
var orphans = Object.keys(this.orphans);
|
var orphans = Object.keys(this.orphans);
|
||||||
var i, hash;
|
var i, hash;
|
||||||
|
|
||||||
(function next(err) {
|
while (this.totalOrphans > constants.mempool.MAX_ORPHAN_TX) {
|
||||||
if (err)
|
|
||||||
return callback(err);
|
|
||||||
|
|
||||||
if (self.totalOrphans <= constants.mempool.MAX_ORPHAN_TX)
|
|
||||||
return callback();
|
|
||||||
|
|
||||||
i = bcoin.ec.rand(0, orphans.length);
|
i = bcoin.ec.rand(0, orphans.length);
|
||||||
hash = orphans[i];
|
hash = orphans[i];
|
||||||
orphans.splice(i, 1);
|
orphans.splice(i, 1);
|
||||||
|
|
||||||
bcoin.debug('Removing orphan %s from mempool.', utils.revHex(hash));
|
bcoin.debug('Removing orphan %s from mempool.', utils.revHex(hash));
|
||||||
|
|
||||||
self.removeOrphan(hash, next);
|
this.removeOrphan(hash);
|
||||||
})();
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -606,20 +600,13 @@ Mempool.prototype.getRange = function getRange(options, callback) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
Mempool.prototype.has = function has(hash, callback) {
|
Mempool.prototype.has = function has(hash, callback) {
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if (this.locker.hasPending(hash))
|
if (this.locker.hasPending(hash))
|
||||||
return utils.asyncify(callback)(null, true);
|
return utils.asyncify(callback)(null, true);
|
||||||
|
|
||||||
return this.hasTX(hash, function(err, exists) {
|
if (this.hasOrphan(hash))
|
||||||
if (err)
|
return utils.asyncify(callback)(null, true);
|
||||||
return callback(err);
|
|
||||||
|
|
||||||
if (exists)
|
return this.hasTX(hash, callback);
|
||||||
return callback(null, true);
|
|
||||||
|
|
||||||
self.hasOrphan(hash, callback);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -722,8 +709,10 @@ Mempool.prototype.addTX = function addTX(tx, callback, force) {
|
|||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
if (!tx.hasCoins())
|
if (!tx.hasCoins()) {
|
||||||
return self.storeOrphan(tx, callback);
|
self.storeOrphan(tx);
|
||||||
|
return callback();
|
||||||
|
}
|
||||||
|
|
||||||
entry = MempoolEntry.fromTX(tx, self.chain.height);
|
entry = MempoolEntry.fromTX(tx, self.chain.height);
|
||||||
|
|
||||||
@ -769,6 +758,7 @@ Mempool.prototype.addTX = function addTX(tx, callback, force) {
|
|||||||
|
|
||||||
Mempool.prototype.addUnchecked = function addUnchecked(entry, callback, force) {
|
Mempool.prototype.addUnchecked = function addUnchecked(entry, callback, force) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
var resolved;
|
||||||
|
|
||||||
var unlock = this._lock(addUnchecked, [entry, callback], force);
|
var unlock = this._lock(addUnchecked, [entry, callback], force);
|
||||||
if (!unlock)
|
if (!unlock)
|
||||||
@ -788,34 +778,31 @@ Mempool.prototype.addUnchecked = function addUnchecked(entry, callback, force) {
|
|||||||
|
|
||||||
bcoin.debug('Added tx %s to the mempool.', entry.tx.rhash);
|
bcoin.debug('Added tx %s to the mempool.', entry.tx.rhash);
|
||||||
|
|
||||||
self.resolveOrphans(entry.tx, function(err, resolved) {
|
resolved = self.resolveOrphans(entry.tx);
|
||||||
if (err)
|
|
||||||
return callback(err);
|
|
||||||
|
|
||||||
utils.forEachSerial(resolved, function(tx, next) {
|
utils.forEachSerial(resolved, function(tx, next) {
|
||||||
var entry = MempoolEntry.fromTX(tx, self.chain.height);
|
var entry = MempoolEntry.fromTX(tx, self.chain.height);
|
||||||
self.verify(entry, function(err) {
|
self.verify(entry, function(err) {
|
||||||
|
if (err) {
|
||||||
|
if (err.type === 'VerifyError') {
|
||||||
|
bcoin.debug('Could not resolve orphan %s: %s.',
|
||||||
|
tx.rhash,
|
||||||
|
err.message);
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
self.emit('error', err);
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
self.addUnchecked(entry, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
if (err.type === 'VerifyError') {
|
|
||||||
bcoin.debug('Could not resolve orphan %s: %s.',
|
|
||||||
tx.rhash,
|
|
||||||
err.message);
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
self.emit('error', err);
|
self.emit('error', err);
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
self.addUnchecked(entry, function(err) {
|
bcoin.debug('Resolved orphan %s in mempool.', entry.tx.rhash);
|
||||||
if (err) {
|
next();
|
||||||
self.emit('error', err);
|
}, true);
|
||||||
return next();
|
});
|
||||||
}
|
}, callback);
|
||||||
bcoin.debug('Resolved orphan %s in mempool.', entry.tx.rhash);
|
|
||||||
next();
|
|
||||||
}, true);
|
|
||||||
});
|
|
||||||
}, callback);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -840,31 +827,28 @@ Mempool.prototype.removeUnchecked = function removeUnchecked(entry, limit, callb
|
|||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
self.removeOrphan(entry.tx, function(err) {
|
self.removeOrphan(entry.tx);
|
||||||
|
|
||||||
|
self._removeUnchecked(entry, limit, function(err) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
self._removeUnchecked(entry, limit, function(err) {
|
self.spent -= entry.tx.inputs.length;
|
||||||
if (err)
|
self.size -= self.memUsage(entry.tx);
|
||||||
return callback(err);
|
self.total--;
|
||||||
|
|
||||||
self.spent -= entry.tx.inputs.length;
|
if (limit) {
|
||||||
self.size -= self.memUsage(entry.tx);
|
rate = Math.floor(entry.fees * 1000 / entry.size);
|
||||||
self.total--;
|
rate += self.minReasonableFee;
|
||||||
|
if (rate > self.minFeeRate) {
|
||||||
if (limit) {
|
self.minFeeRate = rate;
|
||||||
rate = Math.floor(entry.fees * 1000 / entry.size);
|
self.blockSinceBump = false;
|
||||||
rate += self.minReasonableFee;
|
|
||||||
if (rate > self.minFeeRate) {
|
|
||||||
self.minFeeRate = rate;
|
|
||||||
self.blockSinceBump = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.emit('remove tx', entry.tx);
|
self.emit('remove tx', entry.tx);
|
||||||
|
|
||||||
return callback();
|
return callback();
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -1097,17 +1081,15 @@ Mempool.prototype.countAncestors = function countAncestors(tx, callback) {
|
|||||||
/**
|
/**
|
||||||
* Store an orphaned transaction.
|
* Store an orphaned transaction.
|
||||||
* @param {TX} tx
|
* @param {TX} tx
|
||||||
* @param {Function} callback
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Mempool.prototype.storeOrphan = function storeOrphan(tx, callback, force) {
|
Mempool.prototype.storeOrphan = function storeOrphan(tx) {
|
||||||
var self = this;
|
|
||||||
var prevout = {};
|
var prevout = {};
|
||||||
var i, hash, batch, input, prev;
|
var i, hash, batch, input, prev;
|
||||||
|
|
||||||
if (tx.getSize() > 5000) {
|
if (tx.getSize() > 5000) {
|
||||||
bcoin.debug('Ignoring large orphan: %s', tx.rhash);
|
bcoin.debug('Ignoring large orphan: %s', tx.rhash);
|
||||||
return callback();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
hash = tx.hash('hex');
|
hash = tx.hash('hex');
|
||||||
@ -1134,10 +1116,7 @@ Mempool.prototype.storeOrphan = function storeOrphan(tx, callback, force) {
|
|||||||
|
|
||||||
bcoin.debug('Added orphan %s to mempool.', tx.rhash);
|
bcoin.debug('Added orphan %s to mempool.', tx.rhash);
|
||||||
|
|
||||||
if (this.totalOrphans > constants.mempool.MAX_ORPHAN_TX)
|
this.limitOrphans();
|
||||||
return this.limitOrphans(callback);
|
|
||||||
|
|
||||||
return callback();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1187,45 +1166,38 @@ Mempool.prototype.getHistory = function getHistory(callback) {
|
|||||||
}, callback);
|
}, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get hashes of all orphans a transaction hash potentially resolves.
|
|
||||||
* @param {Hash} hash - Resolving transaction.
|
|
||||||
* @param {Function} callback - Return [Error, {@link Hash}[]].
|
|
||||||
*/
|
|
||||||
|
|
||||||
Mempool.prototype.getWaiting = function getWaiting(hash, callback) {
|
|
||||||
return callback(null, this.waiting[hash] || []);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve an orphan transaction.
|
* Retrieve an orphan transaction.
|
||||||
* @param {Hash} orphanHash
|
* @param {Hash} hash
|
||||||
* @param {Function} callback - Returns [Error, {@link TX}].
|
* @returns {TX}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Mempool.prototype.getOrphan = function getOrphan(orphanHash, callback) {
|
Mempool.prototype.getOrphan = function getOrphan(hash) {
|
||||||
var self = this;
|
var orphan = this.orphans[hash];
|
||||||
var orphan = this.orphans[orphanHash];
|
|
||||||
|
|
||||||
if (!orphan)
|
if (!orphan)
|
||||||
return callback();
|
return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
orphan = bcoin.tx.fromExtended(orphan, true);
|
orphan = bcoin.tx.fromExtended(orphan, true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return callback(e);
|
delete this.orphans[hash];
|
||||||
|
bcoin.debug('%s %s',
|
||||||
|
'Warning: possible memory corruption.',
|
||||||
|
'Orphan failed deserialization.');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(null, orphan);
|
return orphan;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Hash} orphanHash
|
* @param {Hash} hash
|
||||||
* @param {Function} callback - Returns [Error, Boolean].
|
* @returns {Boolean}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Mempool.prototype.hasOrphan = function hasOrphan(orphanHash, callback) {
|
Mempool.prototype.hasOrphan = function hasOrphan(hash) {
|
||||||
return callback(null, this.orphans[orphanHash] != null);
|
return this.orphans[hash] != null;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1234,107 +1206,81 @@ Mempool.prototype.hasOrphan = function hasOrphan(orphanHash, callback) {
|
|||||||
* Deletes all orphan entries and
|
* Deletes all orphan entries and
|
||||||
* returns orphan hashes.
|
* returns orphan hashes.
|
||||||
* @param {TX} tx
|
* @param {TX} tx
|
||||||
* @param {Function} callback - Returns [Error, {@link Hash}[]].
|
* @returns {Array} Resolved
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Mempool.prototype.resolveOrphans = function resolveOrphans(tx, callback, force) {
|
Mempool.prototype.resolveOrphans = function resolveOrphans(tx) {
|
||||||
var self = this;
|
|
||||||
var hash = tx.hash('hex');
|
var hash = tx.hash('hex');
|
||||||
var resolved = [];
|
var resolved = [];
|
||||||
|
var hashes = this.waiting[hash];
|
||||||
|
var i, orphanHash, orphan;
|
||||||
|
|
||||||
this.getWaiting(hash, function(err, hashes) {
|
if (!hashes)
|
||||||
if (err)
|
return resolved;
|
||||||
return callback(err);
|
|
||||||
|
|
||||||
utils.forEachSerial(hashes, function(orphanHash, next, i) {
|
for (i = 0; i < hashes.length; i++) {
|
||||||
self.getOrphan(orphanHash, function(err, orphan) {
|
orphanHash = hashes[i];
|
||||||
if (err)
|
orphan = this.getOrphan(orphanHash);
|
||||||
return next(err);
|
|
||||||
|
|
||||||
if (!orphan)
|
if (!orphan)
|
||||||
return next();
|
continue;
|
||||||
|
|
||||||
orphan.fillCoins(tx);
|
orphan.fillCoins(tx);
|
||||||
|
|
||||||
if (orphan.hasCoins()) {
|
if (orphan.hasCoins()) {
|
||||||
self.totalOrphans--;
|
this.totalOrphans--;
|
||||||
delete self.orphans[orphanHash];
|
delete this.orphans[orphanHash];
|
||||||
resolved.push(orphan);
|
resolved.push(orphan);
|
||||||
return next();
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.orphans[orphanHash] = orphan.toExtended(true);
|
this.orphans[orphanHash] = orphan.toExtended(true);
|
||||||
|
}
|
||||||
|
|
||||||
next();
|
delete this.waiting[hash];
|
||||||
});
|
|
||||||
}, function(err) {
|
|
||||||
if (err)
|
|
||||||
return callback(err);
|
|
||||||
|
|
||||||
delete self.waiting[hash];
|
return resolved;
|
||||||
|
|
||||||
return callback(null, resolved);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a transaction from the mempool.
|
* Remove a transaction from the mempool.
|
||||||
* @param {TX|Hash} tx
|
* @param {TX|Hash} tx
|
||||||
* @param {Function} callback
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Mempool.prototype.removeOrphan = function removeOrphan(tx, callback) {
|
Mempool.prototype.removeOrphan = function removeOrphan(tx) {
|
||||||
var self = this;
|
var i, j, hashes, prevout, prev, hash;
|
||||||
var i, prevout, hash;
|
|
||||||
|
|
||||||
function getOrphan(tx, callback) {
|
if (typeof tx === 'string')
|
||||||
if (typeof tx === 'string')
|
tx = this.getOrphan(tx);
|
||||||
return self.getOrphan(tx, callback);
|
|
||||||
return callback(null, tx);
|
if (!tx)
|
||||||
|
return;
|
||||||
|
|
||||||
|
hash = tx.hash('hex');
|
||||||
|
prevout = tx.getPrevout();
|
||||||
|
|
||||||
|
for (i = 0; i < prevout.length; i++) {
|
||||||
|
prev = prevout[i];
|
||||||
|
hashes = this.waiting[prev];
|
||||||
|
|
||||||
|
if (!hashes)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
j = hashes.indexOf(hash);
|
||||||
|
if (j !== -1)
|
||||||
|
hashes.splice(j, 1);
|
||||||
|
|
||||||
|
if (hashes.length === 0) {
|
||||||
|
delete this.waiting[prev];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.waiting[prev] = hashes;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getOrphan(tx, function(err, tx) {
|
delete this.orphans[hash];
|
||||||
if (err)
|
|
||||||
return callback(err);
|
|
||||||
|
|
||||||
if (!tx)
|
this.totalOrphans--;
|
||||||
return callback();
|
|
||||||
|
|
||||||
hash = tx.hash('hex');
|
|
||||||
prevout = tx.getPrevout();
|
|
||||||
|
|
||||||
utils.forEachSerial(prevout, function(prev, next) {
|
|
||||||
self.getWaiting(prev, function(err, hashes) {
|
|
||||||
if (err)
|
|
||||||
return next(err);
|
|
||||||
|
|
||||||
if (hashes.length === 0)
|
|
||||||
return next();
|
|
||||||
|
|
||||||
i = hashes.indexOf(hash);
|
|
||||||
if (i !== -1)
|
|
||||||
hashes.splice(i, 1);
|
|
||||||
|
|
||||||
if (hashes.length === 0) {
|
|
||||||
delete self.waiting[prev];
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.waiting[prev] = hashes;
|
|
||||||
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
}, function(err) {
|
|
||||||
if (err)
|
|
||||||
return callback(err);
|
|
||||||
|
|
||||||
delete self.orphans[hash];
|
|
||||||
self.totalOrphans--;
|
|
||||||
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user