From 557c1044cc9fc1769985e3639b9548fb97937b11 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 19 Oct 2016 05:19:21 -0700 Subject: [PATCH] txdb: new orphan resolution. --- lib/wallet/txdb.js | 101 ++++++++++++++++++++++++++++++++++++++------ test/wallet-test.js | 84 +++++++++++++++++++----------------- 2 files changed, 134 insertions(+), 51 deletions(-) diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index eaa4ab8d..629fabb8 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -514,7 +514,9 @@ TXDB.prototype.verifyInputs = co(function* verifyInputs(tx) { if (spent) { coin = yield this.getSpentCoin(spent, prevout); - assert(coin); + + if (!coin) + continue; if (this.options.verify && tx.height === -1) { input.coin = coin; @@ -681,6 +683,66 @@ TXDB.prototype.unspendCredit = function unspendCredit(tx, i) { this.del(layout.d(spender.hash, spender.index)); }; +/** + * Write input record. + * @param {TX} tx + * @param {Number} i + */ + +TXDB.prototype.writeInput = function writeInput(tx, i) { + var prevout = tx.inputs[i].prevout; + var spender = Outpoint.fromTX(tx, i); + this.put(layout.s(prevout.hash, prevout.index), spender.toRaw()); +}; + +/** + * Remove input record. + * @param {TX} tx + * @param {Number} i + */ + +TXDB.prototype.removeInput = function removeInput(tx, i) { + var prevout = tx.inputs[i].prevout; + this.del(layout.s(prevout.hash, prevout.index)); +}; + +/** + * Resolve orphan input. + * @param {TX} tx + * @param {Number} i + * @param {Path} path + * @returns {Boolean} + */ + +TXDB.prototype.resolveInput = co(function* resolveInput(tx, i, path) { + var hash = tx.hash('hex'); + var spent = yield this.getSpent(hash, i); + var stx, credit; + + if (!spent) + return false; + + if (yield this.hasSpentCoin(spent)) + return false; + + stx = yield this.getTX(spent.hash); + assert(stx); + + credit = Credit.fromTX(tx, i); + + this.spendCredit(credit, stx, spent.index); + + if (stx.height === -1) { + credit.spent = true; + this.saveCredit(credit, path); + this.pending.coin++; + if (tx.height !== -1) + this.pending.confirmed += credit.coin.value; + } + + return true; +}); + /** * Test an entire transaction to see * if any of its outpoints are a double-spend. @@ -847,8 +909,10 @@ TXDB.prototype._insert = co(function* insert(tx) { prevout = input.prevout; credit = yield this.getCredit(prevout.hash, prevout.index); - if (!credit) + if (!credit) { + this.writeInput(tx, i); continue; + } coin = credit.coin; path = yield this.getPath(coin); @@ -880,10 +944,12 @@ TXDB.prototype._insert = co(function* insert(tx) { details.setOutput(i, path); + if (yield this.resolveInput(tx, i, path)) + continue; + credit = Credit.fromTX(tx, i); this.pending.coin++; - this.pending.unconfirmed += output.value; if (tx.height !== -1) @@ -893,25 +959,23 @@ TXDB.prototype._insert = co(function* insert(tx) { } this.put(layout.t(hash), tx.toExtended()); + this.put(layout.m(tx.ps, hash), DUMMY); if (tx.height === -1) this.put(layout.p(hash), DUMMY); else this.put(layout.h(tx.height, hash), DUMMY); - this.put(layout.m(tx.ps, hash), DUMMY); - for (i = 0; i < details.accounts.length; i++) { account = details.accounts[i]; this.put(layout.T(account, hash), DUMMY); + this.put(layout.M(account, tx.ps, hash), DUMMY); if (tx.height === -1) this.put(layout.P(account, hash), DUMMY); else this.put(layout.H(account, tx.height, hash), DUMMY); - - this.put(layout.M(account, tx.ps, hash), DUMMY); } this.pending.tx++; @@ -990,6 +1054,9 @@ TXDB.prototype.confirm = co(function* confirm(tx) { details.setOutput(i, path); + if (yield this.resolveInput(tx, i, path)) + continue; + credit = yield this.getCredit(hash, i); assert(credit); @@ -1105,8 +1172,10 @@ TXDB.prototype._remove = co(function* remove(tx) { input = tx.inputs[i]; credit = credits[i]; - if (!credit) + if (!credit) { + this.removeInput(tx, i); continue; + } coin = credit.coin; path = yield this.getPath(coin); @@ -1147,25 +1216,23 @@ TXDB.prototype._remove = co(function* remove(tx) { } this.del(layout.t(hash)); + this.del(layout.m(tx.ps, hash)); if (tx.height === -1) this.del(layout.p(hash)); else this.del(layout.h(tx.height, hash)); - this.del(layout.m(tx.ps, hash)); - for (i = 0; i < details.accounts.length; i++) { account = details.accounts[i]; this.del(layout.T(account, hash)); + this.del(layout.M(account, tx.ps, hash)); if (tx.height === -1) this.del(layout.P(account, hash)); else this.del(layout.H(account, tx.height, hash)); - - this.del(layout.M(account, tx.ps, hash)); } this.pending.tx--; @@ -2216,6 +2283,16 @@ TXDB.prototype.getSpentCoin = co(function* getSpentCoin(spent, prevout) { return coin; }); +/** + * Test whether the database has a spent coin. + * @param {Outpoint} spent + * @returns {Promise} - Returns {@link Coin}. + */ + +TXDB.prototype.hasSpentCoin = function hasSpentCoin(spent) { + return this.has(layout.d(spent.hash, spent.index)); +}; + /** * Update spent coin height in storage. * @param {TX} tx - Sending transaction. diff --git a/test/wallet-test.js b/test/wallet-test.js index ca488a17..820d2a5b 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -17,24 +17,27 @@ var KEY1 = 'xprv9s21ZrQH143K3Aj6xQBymM31Zb4BVc7wxqfUhMZrzewdDVCt' var KEY2 = 'xprv9s21ZrQH143K3mqiSThzPtWAabQ22Pjp3uSNnZ53A5bQ4udp' + 'faKekc2m4AChLYH1XDzANhrSdxHYWUeTWjYJwFwWFyHkTMnMeAcW4JyRCZa'; -var dummyInput = { - prevout: { - hash: constants.NULL_HASH, - index: 0 - }, - coin: { - version: 1, - height: 0, - value: constants.MAX_MONEY, - script: new bcoin.script([]), - coinbase: false, - hash: constants.NULL_HASH, - index: 0 - }, - script: new bcoin.script([]), - witness: new bcoin.witness([]), - sequence: 0xffffffff -}; +function dummy() { + var hash = crypto.randomBytes(32).toString('hex'); + return { + prevout: { + hash: hash, + index: 0 + }, + coin: { + version: 1, + height: 0, + value: constants.MAX_MONEY, + script: new bcoin.script(), + coinbase: false, + hash: hash, + index: 0 + }, + script: new bcoin.script(), + witness: new bcoin.witness(), + sequence: 0xffffffff + }; +} describe('Wallet', function() { var walletdb, wallet, ewallet, ekey, doubleSpendWallet, doubleSpend; @@ -110,7 +113,7 @@ describe('Wallet', function() { }] }); - src.addInput(dummyInput); + src.addInput(dummy()); tx = bcoin.mtx() .addInput(src, 0) @@ -162,7 +165,7 @@ describe('Wallet', function() { }] }); - src.addInput(dummyInput); + src.addInput(dummy()); tx = bcoin.mtx() .addInput(src, 0) @@ -187,7 +190,7 @@ describe('Wallet', function() { t1 = bcoin.mtx() .addOutput(w.getAddress(), 50000) .addOutput(w.getAddress(), 1000); - t1.addInput(dummyInput); + t1.addInput(dummy()); t1.ts = utils.now(); t1.height = 1; @@ -347,7 +350,7 @@ describe('Wallet', function() { t1 = bcoin.mtx() .addOutput(w.getAddress(), 50000) .addOutput(w.getAddress(), 1000); - t1.addInput(dummyInput); + t1.addInput(dummy()); t1.ts = utils.now(); t1.height = 1; @@ -416,17 +419,20 @@ describe('Wallet', function() { yield walletdb.addTX(t2); balance = yield w.getBalance(); - assert.equal(balance.unconfirmed, 71000); + //assert.equal(balance.unconfirmed, 71000); + assert.equal(balance.unconfirmed, 47000); yield walletdb.addTX(t3); balance = yield w.getBalance(); - assert.equal(balance.unconfirmed, 69000); + //assert.equal(balance.unconfirmed, 69000); + assert.equal(balance.unconfirmed, 22000); yield walletdb.addTX(f1); balance = yield w.getBalance(); - assert.equal(balance.unconfirmed, 58000); + //assert.equal(balance.unconfirmed, 58000); + assert.equal(balance.unconfirmed, 11000); txs = yield w.getHistory(); assert(txs.some(function(tx) { @@ -478,7 +484,7 @@ describe('Wallet', function() { .addOutput(w1.getAddress(), 5460) .addOutput(w1.getAddress(), 5460); - t1.addInput(dummyInput); + t1.addInput(dummy()); t1 = t1.toTX(); yield walletdb.addTX(t1); @@ -520,7 +526,7 @@ describe('Wallet', function() { .addOutput(w1.getAddress(), 5460) .addOutput(w1.getAddress(), 5460); - t1.addInput(dummyInput); + t1.addInput(dummy()); t1 = t1.toTX(); yield walletdb.addTX(t1); @@ -578,7 +584,7 @@ describe('Wallet', function() { .addOutput(w1.getAddress(), 5460) .addOutput(w1.getAddress(), 5460); - t1.addInput(dummyInput); + t1.addInput(dummy()); t1 = t1.toTX(); // Coinbase @@ -588,7 +594,7 @@ describe('Wallet', function() { .addOutput(w2.getAddress(), 5460) .addOutput(w2.getAddress(), 5460); - t2.addInput(dummyInput); + t2.addInput(dummy()); t2 = t2.toTX(); yield walletdb.addTX(t1); @@ -707,7 +713,7 @@ describe('Wallet', function() { else utx.addOutput({ address: addr, value: 5460 * 10 }); - utx.addInput(dummyInput); + utx.addInput(dummy()); utx = utx.toTX(); // Simulate a confirmation @@ -816,7 +822,7 @@ describe('Wallet', function() { .addOutput(rec.getAddress(), 5460) .addOutput(rec.getAddress(), 5460); - t1.addInput(dummyInput); + t1.addInput(dummy()); t1 = t1.toTX(); yield walletdb.addTX(t1); @@ -878,7 +884,7 @@ describe('Wallet', function() { .addOutput(w.getAddress(), 5460) .addOutput(account.receive.getAddress(), 5460); - t1.addInput(dummyInput); + t1.addInput(dummy()); t1 = t1.toTX(); yield walletdb.addTX(t1); @@ -903,7 +909,7 @@ describe('Wallet', function() { .addOutput(account.receive.getAddress(), 5460); t1.ps = 0xdeadbeef; - t1.addInput(dummyInput); + t1.addInput(dummy()); t1 = t1.toTX(); yield walletdb.addTX(t1); @@ -927,7 +933,7 @@ describe('Wallet', function() { .addOutput(w.getAddress(), 5460) .addOutput(w.getAddress(), 5460); - t1.addInput(dummyInput); + t1.addInput(dummy()); t1 = t1.toTX(); yield walletdb.addTX(t1); @@ -963,7 +969,7 @@ describe('Wallet', function() { .addOutput(w1.getAddress(), 5460) .addOutput(w1.getAddress(), 5460); - t1.addInput(dummyInput); + t1.addInput(dummy()); t1 = t1.toTX(); yield walletdb.addTX(t1); @@ -992,7 +998,7 @@ describe('Wallet', function() { .addOutput(w1.getAddress(), 5460) .addOutput(w1.getAddress(), 5460); - t1.addInput(dummyInput); + t1.addInput(dummy()); t1 = t1.toTX(); yield walletdb.addTX(t1); @@ -1065,7 +1071,7 @@ describe('Wallet', function() { .addOutput(key.getAddress(), 5460) .addOutput(key.getAddress(), 5460); - t1.addInput(dummyInput); + t1.addInput(dummy()); t1 = t1.toTX(); yield walletdb.addTX(t1); @@ -1194,7 +1200,7 @@ describe('Wallet', function() { // Coinbase t1 = bcoin.mtx() .addOutput(addr, 50000); - t1.addInput(dummyInput); + t1.addInput(dummy()); t1.height = 1; yield alice.sign(t1); @@ -1267,7 +1273,7 @@ describe('Wallet', function() { // Coinbase t1 = bcoin.mtx() .addOutput(addr, 50000); - t1.addInput(dummyInput); + t1.addInput(dummy()); t1.height = 1; yield alice.sign(t1);