From 2aff6e1de1d014625f2f4aea1f1bfb2c5690a6f7 Mon Sep 17 00:00:00 2001 From: Matthew Zipkin Date: Fri, 20 Mar 2020 12:30:40 -0400 Subject: [PATCH] wallet: check account ownership of name before making transfer TX --- lib/wallet/wallet.js | 13 ++- test/wallet-accounts-auction-test.js | 133 +++++++++++++++++++++++++++ 2 files changed, 144 insertions(+), 2 deletions(-) diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js index 1961636dd..f540293c7 100644 --- a/lib/wallet/wallet.js +++ b/lib/wallet/wallet.js @@ -2626,13 +2626,18 @@ class Wallet extends EventEmitter { * @returns {MTX} */ - async makeTransfer(name, address) { + async makeTransfer(name, address, acct) { assert(typeof name === 'string'); assert(address instanceof Address); if (!rules.verifyName(name)) throw new Error('Invalid name.'); + if (acct != null) { + assert((acct >>> 0) === acct || typeof acct === 'string'); + acct = await this.getAccountIndex(acct); + } + const rawName = Buffer.from(name, 'ascii'); const nameHash = rules.hashName(rawName); const ns = await this.getNameState(nameHash); @@ -2655,6 +2660,9 @@ class Wallet extends EventEmitter { if (coin.height < ns.height) throw new Error(`Wallet does not own: "${name}".`); + if (acct != null && !await this.txdb.hasCoinByAccount(acct, hash, index)) + throw new Error(`Account does not own: "${name}".`); + const state = ns.state(height, network); if (state !== states.CLOSED) @@ -2693,7 +2701,8 @@ class Wallet extends EventEmitter { */ async _createTransfer(name, address, options) { - const mtx = await this.makeTransfer(name, address); + const acct = options ? options.account : null; + const mtx = await this.makeTransfer(name, address, acct); await this.fill(mtx, options); return this.finalize(mtx, options); } diff --git a/test/wallet-accounts-auction-test.js b/test/wallet-accounts-auction-test.js index 42571e061..34ba5de04 100644 --- a/test/wallet-accounts-auction-test.js +++ b/test/wallet-accounts-auction-test.js @@ -596,4 +596,137 @@ describe('Multiple accounts participating in same auction', function() { }); }); }); + + describe('TRANSFER', function() { + // Alice will transfer to Bob + let toAddr; + + before(async () => { + toAddr = await bob.receiveAddress(); + }); + + describe('Library methods', function() { + it('reject from wrongly specified account', async () => { + await assert.rejects(async () => { + await wallet.sendTransfer(name, toAddr, {account: 'bob'}); + }, { + name: 'Error', + message: `Account does not own: "${name}".` + }); + }); + + it('send from correctly specified account', async () => { + const tx = await wallet.sendTransfer(name, toAddr, {account: 0}); + assert(tx); + + await wallet.abandon(tx.hash()); + + assert.strictEqual(node.mempool.map.size, 1); + await node.mempool.reset(); + assert.strictEqual(node.mempool.map.size, 0); + }); + + it('send from correct account automatically', async () => { + const tx = await wallet.sendTransfer(name, toAddr); + assert(tx); + + await wallet.abandon(tx.hash()); + + assert.strictEqual(node.mempool.map.size, 1); + await node.mempool.reset(); + assert.strictEqual(node.mempool.map.size, 0); + }); + }); + + describe('HTTP API', function () { + it('reject from wrongly specified account', async () => { + await assert.rejects(async () => { + await wclient.post(`wallet/${wallet.id}/transfer`, { + name: name, + address: toAddr.toString(network), + account: 'bob' + }); + }, { + name: 'Error', + message: `Account does not own: "${name}".` + }); + }); + + it('send from correctly specified account', async () => { + const tx = await wclient.post(`wallet/${wallet.id}/transfer`, { + name: name, + address: toAddr.toString(network), + account: 'default' + }); + assert(tx); + + await wallet.abandon(Buffer.from(tx.hash, 'hex')); + + assert.strictEqual(node.mempool.map.size, 1); + await node.mempool.reset(); + assert.strictEqual(node.mempool.map.size, 0); + }); + + it('send from correct account automatically', async () => { + const tx = await wclient.post(`wallet/${wallet.id}/transfer`, { + name: name, + address: toAddr.toString(network) + }); + assert(tx); + + await wallet.abandon(Buffer.from(tx.hash, 'hex')); + + assert.strictEqual(node.mempool.map.size, 1); + await node.mempool.reset(); + assert.strictEqual(node.mempool.map.size, 0); + }); + }); + + describe('RPC API', function() { + it('reject from wrongly specified account', async () => { + await wclient.execute('selectwallet', [wallet.id]); + + await assert.rejects(async () => { + await wclient.execute('sendtransfer', [ + name, + toAddr.toString(network), + 'bob' + ]); + }, { + name: 'Error', + message: `Account does not own: "${name}".` + }); + }); + + it('send from correctly specified account', async () => { + const tx = await wclient.execute('sendtransfer', [ + name, + toAddr.toString(network), + 'default' + ]); + assert(tx); + + await wallet.abandon(Buffer.from(tx.hash, 'hex')); + + assert.strictEqual(node.mempool.map.size, 1); + await node.mempool.reset(); + assert.strictEqual(node.mempool.map.size, 0); + }); + + it('send from correct account automatically', async () => { + const tx = await wclient.execute('sendtransfer', [ + name, + toAddr.toString(network) + ]); + assert(tx); + + await wallet.abandon(Buffer.from(tx.hash, 'hex')); + + // This time we will confirm the TRANSFER so we can CANCEL and FINALIZE + assert.strictEqual(node.mempool.map.size, 1); + await mineBlocks(1); + assert.strictEqual(node.mempool.map.size, 0); + }); + }); + }); });