diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index 5f85dd1c..fb440892 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -487,7 +487,7 @@ TXDB.prototype.verifyInputs = co(function* verifyInputs(tx) { input = tx.inputs[i]; prevout = input.prevout; - spent = yield this.isSpent(prevout.hash, prevout.index); + spent = yield this.getSpent(prevout.hash, prevout.index); if (spent) { coin = yield this.getSpentCoin(spent, prevout); @@ -640,7 +640,7 @@ TXDB.prototype.removeConflicts = co(function* removeConflicts(tx, info) { prevout = input.prevout; // Is it already spent? - spent = yield this.isSpent(prevout.hash, prevout.index); + spent = yield this.getSpent(prevout.hash, prevout.index); if (!spent) continue; @@ -857,7 +857,7 @@ TXDB.prototype.removeRecursive = co(function* removeRecursive(tx) { var i, spent, stx, info; for (i = 0; i < tx.outputs.length; i++) { - spent = yield this.isSpent(hash, i); + spent = yield this.getSpent(hash, i); if (!spent) continue; @@ -910,9 +910,8 @@ TXDB.prototype.isDoubleSpend = co(function* isDoubleSpend(tx) { * @returns {Promise} - Returns Boolean. */ -TXDB.prototype.isSpent = co(function* isSpent(hash, index) { - var key = layout.s(hash, index); - var data = yield this.get(key); +TXDB.prototype.getSpent = co(function* getSpent(hash, index) { + var data = yield this.get(layout.s(hash, index)); if (!data) return; @@ -927,7 +926,7 @@ TXDB.prototype.isSpent = co(function* isSpent(hash, index) { * @returns {Promise} - Returns Boolean. */ -TXDB.prototype.isSpending = co(function* isSpending(hash, index) { +TXDB.prototype.getSpending = co(function* getSpending(hash, index) { var key = hash + index; var data = this.spentCache.get(key); @@ -942,6 +941,37 @@ TXDB.prototype.isSpending = co(function* isSpending(hash, index) { return Outpoint.fromRaw(data); }); +/** + * Test a whether a coin has been spent. + * @param {Hash} hash + * @param {Number} index + * @returns {Promise} - Returns Boolean. + */ + +TXDB.prototype.isSpent = co(function* isSpent(hash, index) { + var data = yield this.get(layout.s(hash, index)); + return data != null; +}); + +/** + * Test a whether a coin has been spent. + * @param {Hash} hash + * @param {Number} index + * @returns {Promise} - Returns Boolean. + */ + +TXDB.prototype.isSpending = co(function* isSpending(hash, index) { + var key = hash + index; + var data = this.spentCache.get(key); + + if (data) + return true; + + data = yield this.get(layout.S(hash, index)); + + return data != null; +}); + /** * Attempt to confirm a transaction. * @private @@ -2143,7 +2173,7 @@ TXDB.prototype.getSpentCoin = co(function* getSpentCoin(spent, prevout) { TXDB.prototype.updateSpentCoin = co(function* updateSpentCoin(tx, i) { var prevout = Outpoint.fromTX(tx, i); - var spent = yield this.isSpent(prevout.hash, prevout.index); + var spent = yield this.getSpent(prevout.hash, prevout.index); var coin; if (!spent) diff --git a/test/wallet-test.js b/test/wallet-test.js index 63104135..ca488a17 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -176,7 +176,7 @@ describe('Wallet', function() { assert(tx.verify()); })); - it('should have TX pool and be serializable', cob(function* () { + it('should handle missed and invalid txs', cob(function* () { var w = yield walletdb.create(); var f = yield walletdb.create(); var t1, t2, t3, t4, f1, fake, balance, txs; @@ -188,6 +188,7 @@ describe('Wallet', function() { .addOutput(w.getAddress(), 50000) .addOutput(w.getAddress(), 1000); t1.addInput(dummyInput); + t1.ts = utils.now(); t1.height = 1; // balance: 51000 @@ -228,6 +229,7 @@ describe('Wallet', function() { // balance: 11000 yield w.sign(f1); f1 = f1.toTX(); + fake = bcoin.mtx() .addInput(t1, 1) // 1000 (already redeemed) .addOutput(w.getAddress(), 500); @@ -326,6 +328,144 @@ describe('Wallet', function() { assert.equal(total, 56000); })); + it('should handle missed txs without resolution', cob(function* () { + var walletdb, w, f, t1, t2, t3, t4, f1, fake, balance, txs; + + walletdb = new bcoin.walletdb({ + name: 'wallet-test', + db: 'memory', + resolution: false, + verify: false + }); + + yield walletdb.open(); + + w = yield walletdb.create(); + f = yield walletdb.create(); + + // Coinbase + t1 = bcoin.mtx() + .addOutput(w.getAddress(), 50000) + .addOutput(w.getAddress(), 1000); + t1.addInput(dummyInput); + t1.ts = utils.now(); + t1.height = 1; + + // balance: 51000 + yield w.sign(t1); + t1 = t1.toTX(); + + t2 = bcoin.mtx() + .addInput(t1, 0) // 50000 + .addOutput(w.getAddress(), 24000) + .addOutput(w.getAddress(), 24000); + + // balance: 49000 + yield w.sign(t2); + t2 = t2.toTX(); + t3 = bcoin.mtx() + .addInput(t1, 1) // 1000 + .addInput(t2, 0) // 24000 + .addOutput(w.getAddress(), 23000); + + // balance: 47000 + yield w.sign(t3); + t3 = t3.toTX(); + t4 = bcoin.mtx() + .addInput(t2, 1) // 24000 + .addInput(t3, 0) // 23000 + .addOutput(w.getAddress(), 11000) + .addOutput(w.getAddress(), 11000); + + // balance: 22000 + yield w.sign(t4); + t4 = t4.toTX(); + f1 = bcoin.mtx() + .addInput(t4, 1) // 11000 + .addOutput(f.getAddress(), 10000); + + // balance: 11000 + yield w.sign(f1); + f1 = f1.toTX(); + + // fake = bcoin.mtx() + // .addInput(t1, 1) // 1000 (already redeemed) + // .addOutput(w.getAddress(), 500); + + // Script inputs but do not sign + // yield w.template(fake); + // Fake signature + // fake.inputs[0].script.set(0, constants.ZERO_SIG); + // fake.inputs[0].script.compile(); + // balance: 11000 + // fake = fake.toTX(); + + // Fake TX should temporarly change output + // yield walletdb.addTX(fake); + + yield walletdb.addTX(t4); + + balance = yield w.getBalance(); + assert.equal(balance.unconfirmed, 22000); + + yield walletdb.addTX(t1); + + balance = yield w.getBalance(); + assert.equal(balance.unconfirmed, 73000); + + yield walletdb.addTX(t2); + + balance = yield w.getBalance(); + assert.equal(balance.unconfirmed, 71000); + + yield walletdb.addTX(t3); + + balance = yield w.getBalance(); + assert.equal(balance.unconfirmed, 69000); + + yield walletdb.addTX(f1); + + balance = yield w.getBalance(); + assert.equal(balance.unconfirmed, 58000); + + txs = yield w.getHistory(); + assert(txs.some(function(tx) { + return tx.hash('hex') === f1.hash('hex'); + })); + + balance = yield f.getBalance(); + assert.equal(balance.unconfirmed, 10000); + + txs = yield f.getHistory(); + assert(txs.some(function(tx) { + return tx.hash('hex') === f1.hash('hex'); + })); + + t2.ts = utils.now(); + t2.height = 1; + yield walletdb.addTX(t2); + + t3.ts = utils.now(); + t3.height = 1; + yield walletdb.addTX(t3); + + t4.ts = utils.now(); + t4.height = 1; + yield walletdb.addTX(t4); + + f1.ts = utils.now(); + f1.height = 1; + yield walletdb.addTX(f1); + + balance = yield w.getBalance(); + assert.equal(balance.unconfirmed, 11000); + assert.equal(balance.confirmed, 11000); + + balance = yield f.getBalance(); + assert.equal(balance.unconfirmed, 10000); + assert.equal(balance.confirmed, 10000); + })); + it('should fill tx with inputs', cob(function* () { var w1 = yield walletdb.create(); var w2 = yield walletdb.create(); @@ -1036,10 +1176,16 @@ describe('Wallet', function() { })); it('should recover from a missed tx', cob(function* () { - var alice, addr, bob, t1, t2, t3; + var walletdb, alice, addr, bob, t1, t2, t3; - walletdb.options.verify = false; - walletdb.options.resolution = false; + walletdb = new bcoin.walletdb({ + name: 'wallet-test', + db: 'memory', + resolution: false, + verify: false + }); + + yield walletdb.open(); alice = yield walletdb.create({ master: KEY1 }); bob = yield walletdb.create({ master: KEY1 }); @@ -1103,10 +1249,16 @@ describe('Wallet', function() { })); it('should recover from a missed tx and double spend', cob(function* () { - var alice, addr, bob, t1, t2, t3, t2a; + var walletdb, alice, addr, bob, t1, t2, t3, t2a; - walletdb.options.verify = false; - walletdb.options.resolution = false; + walletdb = new bcoin.walletdb({ + name: 'wallet-test', + db: 'memory', + resolution: false, + verify: false + }); + + yield walletdb.open(); alice = yield walletdb.create({ master: KEY1 }); bob = yield walletdb.create({ master: KEY1 });