From 7506d89947897f3758562e10d99ee705a2520db6 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 6 Oct 2016 03:44:15 -0700 Subject: [PATCH] txdb: improve insertion handling. --- lib/wallet/txdb.js | 93 +++++++++++++++++++++++++++++------------- lib/wallet/wallet.js | 9 ++++ lib/wallet/walletdb.js | 11 +++++ 3 files changed, 84 insertions(+), 29 deletions(-) diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index ba98acc5..f7813db3 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -418,15 +418,17 @@ TXDB.prototype.getOrphans = co(function* getOrphans(hash, index) { */ TXDB.prototype.verify = co(function* verify(tx, info) { + var spends = []; + var orphans = []; + var removed = {}; var i, input, prevout, address, coin, spent, conflict; if (tx.isCoinbase()) - return true; + return orphans; for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; prevout = input.prevout; - address = input.getHash('hex'); // Only bother if this input is ours. @@ -442,21 +444,21 @@ TXDB.prototype.verify = co(function* verify(tx, info) { // Skip invalid transactions if (this.options.verify) { if (!(yield tx.verifyInputAsync(i))) - return false; + return; } continue; } - input.coin = null; - spent = yield this.isSpent(prevout.hash, prevout.index); - // Are we double-spending? - // Replace older txs with newer ones. - if (!spent) + // Orphan until we see a parent transaction. + if (!spent) { + orphans[i] = true; continue; + } + // We must be double-spending. coin = yield this.getSpentCoin(spent, prevout); if (!coin) @@ -467,19 +469,42 @@ TXDB.prototype.verify = co(function* verify(tx, info) { // Skip invalid transactions if (this.options.verify) { if (!(yield tx.verifyInputAsync(i))) - return false; + return; } + 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)); // Remove the older double spender. - conflict = yield this.removeConflict(spent.hash, tx); + // 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. - if (!conflict) - return false; + // Be sure to clear the batch, lest we + // remove other transactions on behalf of + // a non-eligible tx. + if (!conflict) { + this.wallet.clear(); + return; + } this.logger.warning('Removed conflict: %s.', conflict.tx.rhash); @@ -487,7 +512,7 @@ TXDB.prototype.verify = co(function* verify(tx, info) { this.emit('conflict', conflict.tx, conflict.info); } - return true; + return orphans; }); /** @@ -500,7 +525,7 @@ TXDB.prototype.verify = co(function* verify(tx, info) { TXDB.prototype.resolveOrphans = co(function* resolveOrphans(tx, index) { var hash = tx.hash('hex'); - var i, orphans, coin, input, orphan, key; + var i, orphans, coin, input, spender, orphan; orphans = yield this.getOrphans(hash, index); @@ -511,33 +536,39 @@ TXDB.prototype.resolveOrphans = co(function* resolveOrphans(tx, index) { coin = Coin.fromTX(tx, index); - // Add input to orphan + // Add input to resolved orphan. for (i = 0; i < orphans.length; i++) { orphan = orphans[i]; - input = orphan.input; + spender = orphan.input; tx = orphan.tx; // Probably removed by some other means. if (!tx) continue; - tx.inputs[input.index].coin = coin; + input = tx.inputs[spender.index]; + input.coin = coin; - assert(tx.inputs[input.index].prevout.hash === hash); - assert(tx.inputs[input.index].prevout.index === index); + assert(input.prevout.hash === hash); + assert(input.prevout.index === index); // Verify that input script is correct, if not - add // output to unspent and remove orphan from storage - if (!this.options.verify || (yield tx.verifyInputAsync(input.index))) { - key = layout.d(input.hash, input.index); - this.put(key, coin.toRaw()); + if (!this.options.verify || (yield tx.verifyInputAsync(spender.index))) { + // Add the undo coin record which we never had. + this.put(layout.d(spender.hash, spender.index), coin.toRaw()); + // Add the spender record back in case any evil + // transactions were removed with lazyRemove. + this.put(layout.s(hash, index), spender.toRaw()); return true; } yield this.lazyRemove(tx); } - // Just going to be added again outside. + // We had orphans, but they were invalid. The + // balance will be (incorrectly) added outside. + // Subtract to compensate. this.balance.sub(coin); return false; @@ -592,9 +623,9 @@ TXDB.prototype._add = co(function* add(tx, info) { // Verify and get coins. // This potentially removes double-spenders. - result = yield this.verify(tx, info); + orphans = yield this.verify(tx, info); - if (!result) + if (!orphans) return false; hash = tx.hash('hex'); @@ -640,8 +671,9 @@ TXDB.prototype._add = co(function* add(tx, info) { spender = Outpoint.fromTX(tx, i).toRaw(); this.put(layout.s(prevout.hash, prevout.index), spender); - // Add orphan, if no parent transaction is yet known - if (!input.coin) { + // Add orphan if no parent transaction known. + // Do not disconnect any coins. + if (orphans[i]) { yield this.addOrphan(prevout, spender); continue; } @@ -669,6 +701,9 @@ TXDB.prototype._add = co(function* add(tx, info) { orphans = yield this.resolveOrphans(tx, i); + // If this transaction resolves an orphan, + // it should not connect coins as they are + // already spent by the orphan it resolved. if (orphans) continue; @@ -707,7 +742,7 @@ TXDB.prototype._add = co(function* add(tx, info) { * @returns {Promise} - Returns Boolean. */ -TXDB.prototype.removeConflict = co(function* removeConflict(hash, ref) { +TXDB.prototype.removeConflict = co(function* removeConflict(hash, ref, removed) { var tx = yield this.getTX(hash); var info; @@ -736,7 +771,7 @@ TXDB.prototype.removeConflict = co(function* removeConflict(hash, ref) { return; } - info = yield this.removeRecursive(tx); + info = yield this.removeRecursive(tx, removed); return new Conflict(tx, info); }); diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js index f7214e21..54a1ef45 100644 --- a/lib/wallet/wallet.js +++ b/lib/wallet/wallet.js @@ -1017,6 +1017,15 @@ Wallet.prototype.drop = function drop() { return this.db.drop(this); }; +/** + * Clear batch. + * @private + */ + +Wallet.prototype.clear = function clear() { + return this.db.clear(this); +}; + /** * Save batch. * @returns {Promise} diff --git a/lib/wallet/walletdb.js b/lib/wallet/walletdb.js index bc4933f3..a9054ab7 100644 --- a/lib/wallet/walletdb.js +++ b/lib/wallet/walletdb.js @@ -308,6 +308,17 @@ WalletDB.prototype.drop = function drop(wallet) { batch.clear(); }; +/** + * Clear batch. + * @private + * @param {WalletID} wid + */ + +WalletDB.prototype.clear = function clear(wallet) { + var batch = this.batch(wallet); + batch.clear(); +}; + /** * Get batch. * @private