txdb: handle coins and double spenders differently.

This commit is contained in:
Christopher Jeffrey 2016-10-17 07:33:53 -07:00
parent e3a6d7f35e
commit b1996b9717
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD

View File

@ -510,6 +510,9 @@ TXDB.prototype.verifyInputs = co(function* verifyInputs(tx) {
continue; continue;
} }
if (tx.height === -1)
return false;
coin = yield this.getSpentCoin(spent, prevout); coin = yield this.getSpentCoin(spent, prevout);
assert(coin); assert(coin);
@ -625,90 +628,30 @@ TXDB.prototype.resolveOutputs = co(function* resolveOutputs(tx, resolved) {
* @returns {Promise} * @returns {Promise}
*/ */
TXDB.prototype.getInputs = co(function* getInputs(tx, info) { TXDB.prototype.removeConflicts = co(function* removeConflicts(tx, info) {
var spends = []; var hash = tx.hash('hex');
var coins = []; var i, input, prevout, spent;
var removed = {};
var i, input, prevout, coin, spent, conflict;
if (tx.isCoinbase()) if (tx.isCoinbase())
return coins; return;
for (i = 0; i < tx.inputs.length; i++) { for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i]; input = tx.inputs[i];
prevout = input.prevout; prevout = input.prevout;
// Try to get the coin we're redeeming.
coin = yield this.getCoin(prevout.hash, prevout.index);
if (coin) {
input.coin = coin;
coins.push(coin);
continue;
}
// Is it already spent? // Is it already spent?
spent = yield this.isSpent(prevout.hash, prevout.index); spent = yield this.isSpent(prevout.hash, prevout.index);
// If we have no coin or spend
// record, this is not our input.
// This is assuming everything came
// in order!
if (!spent) {
coins.push(null);
continue;
}
// Yikes, we're double spending. We
// still need the coin for after we
// resolve the conflict.
coin = yield this.getSpentCoin(spent, prevout);
assert(coin);
input.coin = coin;
coins.push(coin);
spends[i] = spent;
}
// Once we've verified everything to the
// best of our ability, go through and
// attempt to remove double-spenders.
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
prevout = input.prevout;
spent = spends[i];
if (!spent) if (!spent)
continue; continue;
this.logger.warning('Handling conflicting tx: %s.', // Did _we_ spend it?
utils.revHex(spent.hash)); if (spent.hash === hash)
continue;
// Remove the older double spender. // Remove the double spender.
// We have to maintain a spent list yield this.removeConflict(spent.hash, tx);
// and pass it in. It needs to know
// which txs are considered "deleted"
// within this context.
conflict = yield this.removeConflict(spent.hash, tx, removed);
// Spender was not removed, the current
// transaction is not elligible to be added.
// Be sure to clear the batch, lest we
// remove other transactions on behalf of
// a non-eligible tx.
if (!conflict) {
this.clear();
return;
}
this.logger.warning('Removed conflict: %s.', conflict.tx.rhash);
// Emit the _removed_ transaction.
this.emit('conflict', conflict.tx, conflict.info);
} }
return coins;
}); });
/** /**
@ -751,6 +694,14 @@ TXDB.prototype._add = co(function* add(tx, info) {
assert(!tx.mutable, 'Cannot add mutable TX to wallet.'); assert(!tx.mutable, 'Cannot add mutable TX to wallet.');
if (tx.height === -1) {
if (yield this.isDoubleSpend(tx))
return false;
} else {
// This potentially removes double-spenders.
yield this.removeConflicts(tx, info);
}
// Attempt to confirm tx before adding it. // Attempt to confirm tx before adding it.
result = yield this.confirm(tx, info); result = yield this.confirm(tx, info);
@ -758,13 +709,6 @@ TXDB.prototype._add = co(function* add(tx, info) {
if (result) if (result)
return true; return true;
// Verify and get coins.
// This potentially removes double-spenders.
coins = yield this.getInputs(tx, info);
if (!coins)
return false;
hash = tx.hash('hex'); hash = tx.hash('hex');
this.put(layout.t(hash), tx.toExtended()); this.put(layout.t(hash), tx.toExtended());
@ -794,7 +738,7 @@ TXDB.prototype._add = co(function* add(tx, info) {
for (i = 0; i < tx.inputs.length; i++) { for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i]; input = tx.inputs[i];
prevout = input.prevout; prevout = input.prevout;
coin = coins[i]; coin = yield this.getCoin(prevout.hash, prevout.index);
// Only bother if this input is ours. // Only bother if this input is ours.
if (!coin) if (!coin)
@ -868,37 +812,24 @@ TXDB.prototype._add = co(function* add(tx, info) {
* @returns {Promise} - Returns Boolean. * @returns {Promise} - Returns Boolean.
*/ */
TXDB.prototype.removeConflict = co(function* removeConflict(hash, ref, removed) { TXDB.prototype.removeConflict = co(function* removeConflict(hash, ref) {
var tx = yield this.getTX(hash); var tx = yield this.getTX(hash);
var info; var info;
if (!tx) assert(tx);
throw new Error('Could not find spender.');
if (tx.height !== -1) { this.logger.warning('Handling conflicting tx: %s.', utils.revHex(hash));
// If spender is confirmed and replacement
// is not confirmed, do nothing.
if (ref.height === -1)
return;
// If both are confirmed but replacement this.drop();
// is older than spender, do nothing.
if (ref.height < tx.height)
return;
} else {
// If spender is unconfirmed and replacement
// is confirmed, do nothing.
if (ref.height === -1) {
// If both are unconfirmed but replacement
// is older than spender, do nothing.
if (ref.ps < tx.ps)
return;
}
}
info = yield this.removeRecursive(tx, removed); info = yield this.removeRecursive(tx);
return new Conflict(tx, info); this.start();
this.logger.warning('Removed conflict: %s.', tx.rhash);
// Emit the _removed_ transaction.
this.emit('conflict', tx, info);
}); });
/** /**
@ -909,38 +840,32 @@ TXDB.prototype.removeConflict = co(function* removeConflict(hash, ref, removed)
* @returns {Promise} - Returns Boolean. * @returns {Promise} - Returns Boolean.
*/ */
TXDB.prototype.removeRecursive = co(function* removeRecursive(tx, removed) { TXDB.prototype.removeRecursive = co(function* removeRecursive(tx) {
var hash = tx.hash('hex'); var hash = tx.hash('hex');
var i, spent, stx, info; var i, spent, stx, info;
if (!removed)
removed = {};
for (i = 0; i < tx.outputs.length; i++) { for (i = 0; i < tx.outputs.length; i++) {
spent = yield this.isSpent(hash, i); spent = yield this.isSpent(hash, i);
if (!spent) if (!spent)
continue; continue;
if (removed[spent.hash])
continue;
removed[spent.hash] = true;
// Remove all of the spender's spenders first. // Remove all of the spender's spenders first.
stx = yield this.getTX(spent.hash); stx = yield this.getTX(spent.hash);
if (!stx) assert(stx);
throw new Error('Could not find spender.');
yield this.removeRecursive(stx, removed); yield this.removeRecursive(stx);
} }
this.start();
// Remove the spender. // Remove the spender.
info = yield this.lazyRemove(tx); info = yield this.lazyRemove(tx);
if (!info) assert(info);
throw new Error('Cannot remove spender.');
yield this.commit();
return info; return info;
}); });
@ -1106,8 +1031,12 @@ TXDB.prototype._remove = co(function* remove(hash) {
if (!tx) if (!tx)
return; return;
this.drop();
info = yield this.removeRecursive(tx); info = yield this.removeRecursive(tx);
this.start();
if (!info) if (!info)
return; return;