diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index f692c4c6..4d9e5c87 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -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;