From c0fb984da619319b06eda9a0386f703050e6fcb3 Mon Sep 17 00:00:00 2001 From: Nodar Chkuaselidze Date: Sun, 5 Aug 2018 22:23:52 +0400 Subject: [PATCH] wallet: create non-templated transaction. --- CHANGELOG.md | 9 ++++-- lib/wallet/http.js | 3 ++ lib/wallet/wallet.js | 6 ++++ test/http-test.js | 66 ++++++++++++++++++++++++++++++++++++++++++++ test/wallet-test.js | 34 +++++++++++++++++++++++ 5 files changed, 116 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 292a674e..ca941324 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,13 @@ - `PUT /wallet/:id` Creating a watch-only wallet now requires an `accountKey` argument. This is to prevent bcoin from generating keys and addresses the user can not spend from. -- `POST /wallet/:id/create` Now has a `sign` argument for optional signing - of transactions. +- `POST /wallet/:id/create` + - Now has a `sign` argument for optional signing of transactions. + - Now has a `template` option, that will skip templating inputs when + `sign = false`, but you can enable it if necessary. It does not have an + effect when `sign = true`. + - Exposes `blocks`, which can will be used if there is no `rate` option. + - Exposes `sort` (Default `true`), that can be used to disable BIP69 sorting. #### RPC diff --git a/lib/wallet/http.js b/lib/wallet/http.js index 38d820a5..0dc6232b 100644 --- a/lib/wallet/http.js +++ b/lib/wallet/http.js @@ -468,13 +468,16 @@ class HTTP extends Server { const options = { rate: valid.u64('rate'), + blocks: valid.u32('blocks'), maxFee: valid.u64('maxFee'), selection: valid.str('selection'), smart: valid.bool('smart'), account: valid.str('account'), + sort: valid.bool('sort'), subtractFee: valid.bool('subtractFee'), subtractIndex: valid.i32('subtractIndex'), depth: valid.u32(['confirmations', 'depth']), + template: valid.bool('template', sign), outputs: [] }; diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js index f794e75c..1df16bf3 100644 --- a/lib/wallet/wallet.js +++ b/lib/wallet/wallet.js @@ -1236,6 +1236,9 @@ class Wallet extends EventEmitter { * to avoid sorting), set locktime, and template it. * @param {Object} options - See {@link Wallet#fund options}. * @param {Object[]} options.outputs - See {@link MTX#addOutput}. + * @param {Boolean} options.sort - Sort inputs and outputs (BIP69). + * @param {Boolean} options.template - Build scripts for inputs. + * @param {Number} options.locktime - TX locktime * @returns {Promise} - Returns {@link MTX}. */ @@ -1281,6 +1284,9 @@ class Wallet extends EventEmitter { assert(mtx.verifyInputs(this.wdb.state.height + 1), 'TX failed context check.'); + if (options.template === false) + return mtx; + const total = await this.template(mtx); if (total === 0) diff --git a/test/http-test.js b/test/http-test.js index 9803ae9d..0fe7d88f 100644 --- a/test/http-test.js +++ b/test/http-test.js @@ -264,6 +264,72 @@ describe('HTTP', function() { }); }); + for (const template of [true, false]) { + const suffix = template ? 'with template' : 'without template'; + it(`should create and sign transaction ${suffix}`, async () => { + const change = await wallet.createChange('default'); + const tx = await wallet.createTX({ + template: template, // should not matter, sign = true + sign: true, + outputs: [{ + address: change.address, + value: 50000 + }] + }); + const mtx = MTX.fromJSON(tx); + + for (const input of tx.inputs) { + const script = input.script; + + assert.notStrictEqual(script, '', + 'Input must be signed.'); + } + + assert.strictEqual(mtx.verify(), true, + 'Transaction must be signed.'); + }); + } + + it('should create transaction without template', async () => { + const change = await wallet.createChange('default'); + const tx = await wallet.createTX({ + sign: false, + outputs: [{ + address: change.address, + value: 50000 + }] + }); + + for (const input of tx.inputs) { + const script = input.script; + + assert.strictEqual(script.length, 0, + 'Input must not be templated.'); + } + }); + + it('should create transaction with template', async () => { + const change = await wallet.createChange('default'); + const tx = await wallet.createTX({ + sign: false, + template: true, + outputs: [{ + address: change.address, + value: 20000 + }] + }); + + for (const input of tx.inputs) { + const script = Buffer.from(input.script, 'hex'); + + // p2pkh + // 1 (OP_0 placeholder) + 1 (length) + 33 (pubkey) + assert.strictEqual(script.length, 35); + assert.strictEqual(script[0], 0x00, + 'First item in stack must be a placeholder OP_0'); + } + }); + it('should cleanup', async () => { consensus.COINBASE_MATURITY = 100; await wallet.close(); diff --git a/test/wallet-test.js b/test/wallet-test.js index ffb900af..c21ddc4e 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -1220,6 +1220,40 @@ describe('Wallet', function() { assert(t3.verify()); }); + for (const witness of [true, false]) { + it(`should create non-templated tx (witness=${witness})`, async () => { + const wallet = await wdb.create({ witness }); + + // Fund wallet + const t1 = new MTX(); + t1.addInput(dummyInput()); + t1.addOutput(await wallet.receiveAddress(), 500000); + + await wdb.addTX(t1.toTX()); + + const options = { + rate: 10000, + round: true, + outputs: [{ + address: await wallet.receiveAddress(), + value: 7000 + }], + template: false + }; + + const t2 = await wallet.createTX(options); + + assert(t2, 'Could not create tx.'); + + for (const input of t2.inputs) { + const {script, witness} = input; + + assert.strictEqual(script.length, 0, 'Input is templated.'); + assert.strictEqual(witness.length, 0, 'Input is templated.'); + } + }); + } + it('should get range of txs', async () => { const wallet = currentWallet; const txs = await wallet.getRange(null, {