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;
}
if (tx.height === -1)
return false;
coin = yield this.getSpentCoin(spent, prevout);
assert(coin);
@ -625,90 +628,30 @@ TXDB.prototype.resolveOutputs = co(function* resolveOutputs(tx, resolved) {
* @returns {Promise}
*/
TXDB.prototype.getInputs = co(function* getInputs(tx, info) {
var spends = [];
var coins = [];
var removed = {};
var i, input, prevout, coin, spent, conflict;
TXDB.prototype.removeConflicts = co(function* removeConflicts(tx, info) {
var hash = tx.hash('hex');
var i, input, prevout, spent;
if (tx.isCoinbase())
return coins;
return;
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
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?
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)
continue;
this.logger.warning('Handling conflicting tx: %s.',
utils.revHex(spent.hash));
// Did _we_ spend it?
if (spent.hash === hash)
continue;
// Remove the older double spender.
// We have to maintain a spent list
// 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);
// Remove the double spender.
yield this.removeConflict(spent.hash, tx);
}
return coins;
});
/**
@ -751,6 +694,14 @@ TXDB.prototype._add = co(function* add(tx, info) {
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.
result = yield this.confirm(tx, info);
@ -758,13 +709,6 @@ TXDB.prototype._add = co(function* add(tx, info) {
if (result)
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');
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++) {
input = tx.inputs[i];
prevout = input.prevout;
coin = coins[i];
coin = yield this.getCoin(prevout.hash, prevout.index);
// Only bother if this input is ours.
if (!coin)
@ -868,37 +812,24 @@ TXDB.prototype._add = co(function* add(tx, info) {
* @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 info;
if (!tx)
throw new Error('Could not find spender.');
assert(tx);
if (tx.height !== -1) {
// If spender is confirmed and replacement
// is not confirmed, do nothing.
if (ref.height === -1)
return;
this.logger.warning('Handling conflicting tx: %s.', utils.revHex(hash));
// If both are confirmed but replacement
// 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;
}
}
this.drop();
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.
*/
TXDB.prototype.removeRecursive = co(function* removeRecursive(tx, removed) {
TXDB.prototype.removeRecursive = co(function* removeRecursive(tx) {
var hash = tx.hash('hex');
var i, spent, stx, info;
if (!removed)
removed = {};
for (i = 0; i < tx.outputs.length; i++) {
spent = yield this.isSpent(hash, i);
if (!spent)
continue;
if (removed[spent.hash])
continue;
removed[spent.hash] = true;
// Remove all of the spender's spenders first.
stx = yield this.getTX(spent.hash);
if (!stx)
throw new Error('Could not find spender.');
assert(stx);
yield this.removeRecursive(stx, removed);
yield this.removeRecursive(stx);
}
this.start();
// Remove the spender.
info = yield this.lazyRemove(tx);
if (!info)
throw new Error('Cannot remove spender.');
assert(info);
yield this.commit();
return info;
});
@ -1106,8 +1031,12 @@ TXDB.prototype._remove = co(function* remove(hash) {
if (!tx)
return;
this.drop();
info = yield this.removeRecursive(tx);
this.start();
if (!info)
return;