diff --git a/lib/wallet/records.js b/lib/wallet/records.js index a81ccb8ab..a5d8d0836 100644 --- a/lib/wallet/records.js +++ b/lib/wallet/records.js @@ -422,6 +422,10 @@ class MapRecord extends bio.Struct { return this.wids.delete(wid); } + has(wid) { + return this.wids.has(wid); + } + write(bw) { bw.writeU32(this.wids.size); diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index d5f53d98e..359729a47 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -1045,6 +1045,7 @@ class TXDB { } await this.saveCredit(b, credit, path); + await this.watchOpensEarly(b, output); } // Handle names. @@ -1852,12 +1853,36 @@ class TXDB { } } + /** + * Start tracking OPENs right away. + * This does not check if the name is owned by the wallet. + * @private + * @param {Batch} b + * @param {Output} tx + * @param {Path} path + * @returns {Promise} + */ + + async watchOpensEarly(b, output) { + const {covenant} = output; + + if (!covenant.isName()) + return; + + if (!covenant.isOpen()) + return; + + const nameHash = covenant.getHash(0); + + if (!await this.wdb.hasNameMap(nameHash, this.wid)) + await this.addNameMap(b, nameHash); + } + /** * Handle incoming covenant. * @param {Object} b * @param {TX} tx - * @param {Number} i - * @param {Path} path + * @param {CoinView} view * @param {Number} height * @returns {Promise} updated */ @@ -1916,8 +1941,7 @@ class TXDB { case types.OPEN: { if (!path) { // Are we "watching" this name? - const map = await this.wdb.getNameMap(nameHash); - if (!map || !map.wids.has(this.wid)) + if (!await this.wdb.hasNameMap(nameHash, this.wid)) break; const name = covenant.get(2); diff --git a/lib/wallet/walletdb.js b/lib/wallet/walletdb.js index 14db3d864..32503c0f3 100644 --- a/lib/wallet/walletdb.js +++ b/lib/wallet/walletdb.js @@ -1925,6 +1925,22 @@ class WalletDB extends EventEmitter { return MapRecord.decode(data); } + /** + * Does wdb have wallet map. + * @param {Buffer} key + * @param {Number} wid + * @returns {Promise} + */ + + async hasMap(key, wid) { + const map = await this.getMap(key); + + if (!map) + return false; + + return map.has(wid); + } + /** * Add wid to a wallet map. * @param {Wallet} wallet @@ -2116,6 +2132,17 @@ class WalletDB extends EventEmitter { return this.getMap(layout.N.encode(nameHash)); } + /** + * Has wid in the wallet map. + * @param {Buffer} nameHash + * @param {Number} wid + * @returns {Promise} + */ + + async hasNameMap(nameHash, wid) { + return this.hasMap(layout.N.encode(nameHash), wid); + } + /** * Add wid to a wallet map. * @param {Wallet} wallet diff --git a/test/mempool-invalidation-test.js b/test/mempool-invalidation-test.js index 85c27bffe..67ec153e8 100644 --- a/test/mempool-invalidation-test.js +++ b/test/mempool-invalidation-test.js @@ -137,8 +137,8 @@ describe('Mempool Invalidation', function() { assert.strictEqual(await getNameState(name), states.OPENING); assert.strictEqual(node.mempool.map.size, 0); - // we don't want coins to get stuck in the wallet. - wallet2.abandon(memopen.hash()); + const pending = await wallet2.getPending(); + assert.strictEqual(pending.length, 0); }); it('should invalidate bids', async () => { diff --git a/test/wallet-auction-test.js b/test/wallet-auction-test.js index 834be5c2e..267e111dd 100644 --- a/test/wallet-auction-test.js +++ b/test/wallet-auction-test.js @@ -18,7 +18,6 @@ const {Resource} = require('../lib/dns/resource'); const {forEvent} = require('./util/common'); const network = Network.get('regtest'); -const NAME1 = rules.grindName(10, 2, network); const { treeInterval, biddingPeriod, @@ -58,7 +57,10 @@ const wdb = new WalletDB({ }); describe('Wallet Auction', function() { - let wallet; + let wallet, wallet2; + + const name1 = rules.grindName(10, 2, network); + const name2 = rules.grindName(10, 2, network); before(async () => { // Open @@ -70,6 +72,7 @@ describe('Wallet Auction', function() { // Set up wallet wallet = await wdb.create(); + wallet2 = await wdb.create(); chain.on('connect', async (entry, block) => { await wdb.addBlock(entry, block.txs); }); @@ -79,10 +82,16 @@ describe('Wallet Auction', function() { }); // Generate blocks to roll out name and fund wallet - let winnerAddr = await wallet.createReceive(); - winnerAddr = winnerAddr.getAddress().toString(network); - for (let i = 0; i < 10; i++) { - const block = await cpu.mineBlock(null, winnerAddr); + let walletAddr = await wallet.createReceive(); + walletAddr = walletAddr.getAddress().toString(network); + for (let i = 0; i < 5; i++) { + const block = await cpu.mineBlock(null, walletAddr); + await chain.add(block); + } + + walletAddr = (await wallet2.createReceive()).getAddress().toString(network); + for (let i = 0; i < 5; i++) { + const block = await cpu.mineBlock(null, walletAddr); await chain.add(block); } }); @@ -108,7 +117,7 @@ describe('Wallet Auction', function() { it('should open auction', async () => { for (let i = 0; i < OPENS1; i++) { - const open = await wallet.createOpen(NAME1); + const open = await wallet.createOpen(name1); await wallet.sign(open); assert.strictEqual(open.inputs.length, 1); @@ -133,13 +142,13 @@ describe('Wallet Auction', function() { it('should fail to create duplicate open', async () => { let err; try { - await wallet.createOpen(NAME1); + await wallet.createOpen(name1); } catch (e) { err = e; } assert(err); - assert.strictEqual(err.message, `Already sent an open for: ${NAME1}.`); + assert.strictEqual(err.message, `Already sent an open for: ${name1}.`); }); it('should not accept own duplicate open', async () => { @@ -183,13 +192,13 @@ describe('Wallet Auction', function() { it('should fail to re-open auction during OPEN phase', async () => { let err; try { - await wallet.createOpen(NAME1); + await wallet.createOpen(name1); } catch (e) { err = e; } assert(err); - assert.strictEqual(err.message, `Name is already opening: ${NAME1}.`); + assert.strictEqual(err.message, `Name is already opening: ${name1}.`); }); it('should mine enough blocks to enter BIDDING phase', async () => { @@ -201,7 +210,7 @@ describe('Wallet Auction', function() { }); it('should fail to send bid to null address', async () => { - const mtx = await wallet.makeBid(NAME1, 1000, 2000, 0); + const mtx = await wallet.makeBid(name1, 1000, 2000, 0); mtx.outputs[0].address = new Address(); await wallet.fill(mtx); await wallet.finalize(mtx); @@ -214,13 +223,13 @@ describe('Wallet Auction', function() { it('should fail to re-open auction during BIDDING phase', async () => { let err; try { - await wallet.createOpen(NAME1); + await wallet.createOpen(name1); } catch (e) { err = e; } assert(err); - assert.strictEqual(err.message, `Name is not available: ${NAME1}.`); + assert.strictEqual(err.message, `Name is not available: ${name1}.`); }); it('should mine enough blocks to expire auction', async () => { @@ -241,13 +250,13 @@ describe('Wallet Auction', function() { it('should fail to create duplicate open (again)', async () => { let err; try { - await wallet.createOpen(NAME1); + await wallet.createOpen(name1); } catch (e) { err = e; } assert(err); - assert.strictEqual(err.message, `Already sent an open for: ${NAME1}.`); + assert.strictEqual(err.message, `Already sent an open for: ${name1}.`); }); it('should confirm OPEN transaction', async () => { @@ -259,7 +268,7 @@ describe('Wallet Auction', function() { const block = await job.mineAsync(); assert(await chain.add(block)); - let ns = await chain.db.getNameStateByName(NAME1); + let ns = await chain.db.getNameStateByName(name1); let state = ns.state(chain.height, network); assert.strictEqual(state, states.OPENING); @@ -269,7 +278,7 @@ describe('Wallet Auction', function() { assert(await chain.add(block)); } - ns = await chain.db.getNameStateByName(NAME1); + ns = await chain.db.getNameStateByName(name1); state = ns.state(chain.height, network); assert.strictEqual(state, states.BIDDING); }); @@ -355,6 +364,31 @@ describe('Wallet Auction', function() { const secondTX = await wallet.getTX(openTXs[insertIndexes[2]].hash()); assert.notStrictEqual(secondTX, null); }); + + it('should handle foreign double open after sending open', async () => { + const open = await wallet.createOpen(name2); + await wallet.sign(open); + + const open2 = await wallet2.createOpen(name2); + await wallet2.sign(open2); + + // try to open. + await wdb.addTX(open.toTX()); + const pending1 = await wallet.getPending(); + assert.strictEqual(pending1.length, 1); + assert.bufferEqual(pending1[0].hash, open.hash()); + + const job = await cpu.createJob(); + const [tx, view] = open2.commit(); + job.addTX(tx, view); + job.refresh(); + + const block = await job.mineAsync(); + assert(await chain.add(block)); + + const pending1after = await wallet.getPending(); + assert.strictEqual(pending1after.length, 0); + }); }); describe('Batch TXs', function() {