diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index d23372328..2db375b66 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -993,6 +993,12 @@ class TXDB { if (!path) continue; + // If the first time we see a TX is in a block + // (i.e. during a rescan) update the "unconfirmed" locked balance + // before updating the "confirmed" locked balance. + if (height !== -1) + await this.lockBalances(b, state, tx, i, path, -1); + await this.lockBalances(b, state, tx, i, path, height); details.setOutput(i, path); diff --git a/test/wallet-test.js b/test/wallet-test.js index 8be874ccd..42d819c48 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -1,5 +1,6 @@ /* eslint-env mocha */ /* eslint prefer-arrow-callback: "off" */ +/* eslint no-implicit-coercion: "off" */ 'use strict'; @@ -1642,4 +1643,298 @@ describe('Wallet', function() { } }); }); + + describe('TXDB locked balance', function() { + const network = Network.get('regtest'); + const workers = new WorkerPool({ enabled }); + const wdb = new WalletDB({ network, workers }); + const name = 'satoshi'; + const value = 1e6; + const lockup = 2e6; + const fee = 10000; + let wallet; + + before(async () => { + await wdb.open(); + wallet = await wdb.create(); + // rollout all names + wdb.height = 52 * 144 * 7; + }); + + after(async () => { + await wdb.close(); + }); + + it('should fund wallet', async () => { + const addr = await wallet.receiveAddress(); + + // Fund wallet + const mtx = new MTX(); + mtx.addOutpoint(new Outpoint(Buffer.alloc(32), 0)); + mtx.addOutput(addr, 10e6); + const tx = mtx.toTX(); + + // Dummy block + const block = { + height: wdb.height + 1, + hash: Buffer.alloc(32), + time: Date.now() + }; + + // Add confirmed funding TX to wallet + await wallet.txdb.add(tx, block); + + // Check + const bal = await wallet.getBalance(); + assert.strictEqual(bal.tx, 1); + assert.strictEqual(bal.coin, 1); + assert.strictEqual(bal.confirmed, 10e6); + assert.strictEqual(bal.unconfirmed, 10e6); + assert.strictEqual(bal.ulocked, 0); + assert.strictEqual(bal.clocked, 0); + }); + + it('should send and confirm OPEN', async () => { + const open = await wallet.sendOpen(name, false, {hardFee: fee}); + + // Check + let bal = await wallet.getBalance(); + assert.strictEqual(bal.tx, 2); + assert.strictEqual(bal.coin, 2); + assert.strictEqual(bal.confirmed, 10e6); + assert.strictEqual(bal.unconfirmed, 10e6 - fee); + assert.strictEqual(bal.ulocked, 0); + assert.strictEqual(bal.clocked, 0); + + // Confirm OPEN + const block = { + height: wdb.height + 1, + hash: Buffer.alloc(32), + time: Date.now() + }; + await wallet.txdb.add(open, block); + + // Check + bal = await wallet.getBalance(); + assert.strictEqual(bal.tx, 2); + assert.strictEqual(bal.coin, 2); + assert.strictEqual(bal.confirmed, 10e6 - (1 * fee)); + assert.strictEqual(bal.unconfirmed, 10e6 - (1 * fee)); + assert.strictEqual(bal.ulocked, 0); + assert.strictEqual(bal.clocked, 0); + }); + + it('should send and confirm BID', async () => { + // Advance to bidding + wdb.height += network.names.treeInterval + 1; + + const bid = await wallet.sendBid(name, value, lockup, {hardFee: fee}); + + // Check + let bal = await wallet.getBalance(); + assert.strictEqual(bal.tx, 3); + assert.strictEqual(bal.coin, 3); + assert.strictEqual(bal.confirmed, 10e6 - (1 * fee)); + assert.strictEqual(bal.unconfirmed, 10e6 - (2 * fee)); + assert.strictEqual(bal.ulocked, lockup); + assert.strictEqual(bal.clocked, 0); + + // Confirm BID + const block = { + height: wdb.height + 1, + hash: Buffer.alloc(32), + time: Date.now() + }; + await wallet.txdb.add(bid, block); + + // Check + bal = await wallet.getBalance(); + assert.strictEqual(bal.tx, 3); + assert.strictEqual(bal.coin, 3); + assert.strictEqual(bal.confirmed, 10e6 - (2 * fee)); + assert.strictEqual(bal.unconfirmed, 10e6 - (2 * fee)); + assert.strictEqual(bal.ulocked, lockup); + assert.strictEqual(bal.clocked, lockup); + }); + + it('should send and confirm REVEAL', async () => { + // Advance to reveal + wdb.height += network.names.biddingPeriod; + + const reveal = await wallet.sendReveal(name, {hardFee: fee}); + + // Check + let bal = await wallet.getBalance(); + assert.strictEqual(bal.tx, 4); + assert.strictEqual(bal.coin, 4); + assert.strictEqual(bal.confirmed, 10e6 - (2 * fee)); + assert.strictEqual(bal.unconfirmed, 10e6 - (3 * fee)); + assert.strictEqual(bal.ulocked, value); + assert.strictEqual(bal.clocked, lockup); + + // Confirm BID + const block = { + height: wdb.height + 1, + hash: Buffer.alloc(32), + time: Date.now() + }; + await wallet.txdb.add(reveal, block); + + // Check + bal = await wallet.getBalance(); + assert.strictEqual(bal.tx, 4); + assert.strictEqual(bal.coin, 4); + assert.strictEqual(bal.confirmed, 10e6 - (3 * fee)); + assert.strictEqual(bal.unconfirmed, 10e6 - (3 * fee)); + assert.strictEqual(bal.ulocked, value); + assert.strictEqual(bal.clocked, value); + }); + }); + + describe('TXDB locked balance after simulated rescan', function() { + const network = Network.get('regtest'); + const workers = new WorkerPool({ enabled }); + const wdb = new WalletDB({ network, workers }); + const name = 'satoshi'; + const value = 1e6; + const lockup = 2e6; + const fee = 10000; + let wallet; + + before(async () => { + await wdb.open(); + wallet = await wdb.create(); + // rollout all names + wdb.height = 52 * 144 * 7; + }); + + after(async () => { + await wdb.close(); + }); + + it('should fund wallet', async () => { + const addr = await wallet.receiveAddress(); + + // Fund wallet + const mtx = new MTX(); + mtx.addOutpoint(new Outpoint(Buffer.alloc(32), 0)); + mtx.addOutput(addr, 10e6); + const tx = mtx.toTX(); + + // Dummy block + const block = { + height: wdb.height + 1, + hash: Buffer.alloc(32), + time: Date.now() + }; + + // Add confirmed funding TX to wallet + await wallet.txdb.add(tx, block); + + // Check + const bal = await wallet.getBalance(); + assert.strictEqual(bal.tx, 1); + assert.strictEqual(bal.coin, 1); + assert.strictEqual(bal.confirmed, 10e6); + assert.strictEqual(bal.unconfirmed, 10e6); + assert.strictEqual(bal.ulocked, 0); + assert.strictEqual(bal.clocked, 0); + }); + + it('should confirm new OPEN', async () => { + const open = await wallet.createOpen(name, false, {hardFee: fee}); + + // Check + let bal = await wallet.getBalance(); + assert.strictEqual(bal.tx, 1); + assert.strictEqual(bal.coin, 1); + assert.strictEqual(bal.confirmed, 10e6); + assert.strictEqual(bal.unconfirmed, 10e6); + assert.strictEqual(bal.ulocked, 0); + assert.strictEqual(bal.clocked, 0); + + // Confirm OPEN + const block = { + height: wdb.height + 1, + hash: Buffer.alloc(32), + time: Date.now() + }; + await wallet.txdb.add(open.toTX(), block); + + // Check + bal = await wallet.getBalance(); + assert.strictEqual(bal.tx, 2); + assert.strictEqual(bal.coin, 2); + assert.strictEqual(bal.confirmed, 10e6 - (1 * fee)); + assert.strictEqual(bal.unconfirmed, 10e6 - (1 * fee)); + assert.strictEqual(bal.ulocked, 0); + assert.strictEqual(bal.clocked, 0); + }); + + it('should confirm new BID', async () => { + // Advance to bidding + wdb.height += network.names.treeInterval + 1; + + const bid = await wallet.createBid(name, value, lockup, {hardFee: fee}); + + // Check + let bal = await wallet.getBalance(); + assert.strictEqual(bal.tx, 2); + assert.strictEqual(bal.coin, 2); + assert.strictEqual(bal.confirmed, 10e6 - (1 * fee)); + assert.strictEqual(bal.unconfirmed, 10e6 - (1 * fee)); + assert.strictEqual(bal.ulocked, 0); + assert.strictEqual(bal.clocked, 0); + + // Confirm BID + const block = { + height: wdb.height + 1, + hash: Buffer.alloc(32), + time: Date.now() + }; + await wallet.txdb.add(bid.toTX(), block); + + // Check + bal = await wallet.getBalance(); + assert.strictEqual(bal.tx, 3); + assert.strictEqual(bal.coin, 3); + assert.strictEqual(bal.confirmed, 10e6 - (2 * fee)); + assert.strictEqual(bal.unconfirmed, 10e6 - (2 * fee)); + assert.strictEqual(bal.ulocked, lockup); + assert.strictEqual(bal.clocked, lockup); + }); + + it('should confirm new REVEAL', async () => { + // Advance to reveal + wdb.height += network.names.biddingPeriod; + + const reveal = await wallet.createReveal(name, {hardFee: fee}); + + // Check + let bal = await wallet.getBalance(); + assert.strictEqual(bal.tx, 3); + assert.strictEqual(bal.coin, 3); + assert.strictEqual(bal.confirmed, 10e6 - (2 * fee)); + assert.strictEqual(bal.unconfirmed, 10e6 - (2 * fee)); + assert.strictEqual(bal.ulocked, lockup); + assert.strictEqual(bal.clocked, lockup); + + // Confirm BID + const block = { + height: wdb.height + 1, + hash: Buffer.alloc(32), + time: Date.now() + }; + await wallet.txdb.add(reveal.toTX(), block); + + // Check + bal = await wallet.getBalance(); + assert.strictEqual(bal.tx, 4); + assert.strictEqual(bal.coin, 4); + assert.strictEqual(bal.confirmed, 10e6 - (3 * fee)); + assert.strictEqual(bal.unconfirmed, 10e6 - (3 * fee)); + assert.strictEqual(bal.ulocked, value); + assert.strictEqual(bal.clocked, value); + }); + }); });