txdb: handle coins and double spenders differently.
This commit is contained in:
parent
e3a6d7f35e
commit
b1996b9717
@ -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;
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user