From b92fd915ebedfb5e11efe93cc87d5a7d9a058671 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Fri, 27 Feb 2015 18:27:56 -0300 Subject: [PATCH 1/2] increase coverage for Input and Output, fix some bugs --- lib/transaction/input/input.js | 37 +++++++++++++++------------- lib/transaction/output.js | 4 +-- lib/transaction/transaction.js | 3 --- test/transaction/input/input.js | 43 ++++++++++++++++++++++++++++++++- test/transaction/output.js | 39 +++++++++++++++++++++++++++--- 5 files changed, 99 insertions(+), 27 deletions(-) diff --git a/lib/transaction/input/input.js b/lib/transaction/input/input.js index 47a5c8c91..b9c0d747c 100644 --- a/lib/transaction/input/input.js +++ b/lib/transaction/input/input.js @@ -1,6 +1,7 @@ 'use strict'; var _ = require('lodash'); +var $ = require('../../util/preconditions'); var errors = require('../../errors'); var BufferWriter = require('../../encoding/bufferwriter'); var buffer = require('buffer'); @@ -10,6 +11,9 @@ var Script = require('../../script'); var Sighash = require('../sighash'); var Output = require('../output'); + +var DEFAULT_SEQNUMBER = 0xFFFFFFFF; + function Input(params) { if (!(this instanceof Input)) { return new Input(params); @@ -19,6 +23,8 @@ function Input(params) { } } +Input.DEFAULT_SEQNUMBER = DEFAULT_SEQNUMBER; + Object.defineProperty(Input.prototype, 'script', { configurable: false, writeable: false, @@ -37,9 +43,10 @@ Input.prototype._fromObject = function(params) { } this.output = params.output ? (params.output instanceof Output ? params.output : new Output(params.output)) : undefined; - this.prevTxId = params.prevTxId; - this.outputIndex = params.outputIndex; - this.sequenceNumber = params.sequenceNumber; + this.prevTxId = params.prevTxId || params.txidbuf; + this.outputIndex = _.isUndefined(params.outputIndex) ? params.txoutnum : params.outputIndex; + this.sequenceNumber = _.isUndefined(params.sequenceNumber) ? + (_.isUndefined(params.seqnum) ? DEFAULT_SEQNUMBER : params.seqnum) : params.sequenceNumber; if (_.isUndefined(params.script) && _.isUndefined(params.scriptBuffer)) { throw new errors.Transaction.Input.MissingScript(); } @@ -57,21 +64,19 @@ Input.prototype.toObject = function toObject() { }; }; +Input.fromObject = function(obj) { + $.checkArgument(_.isObject(obj)); + var input = new Input(); + return input._fromObject(obj); +}; + Input.prototype.toJSON = function toJSON() { return JSON.stringify(this.toObject()); }; Input.fromJSON = function(json) { - if (JSUtil.isValidJSON(json)) { - json = JSON.parse(json); - } - return new Input({ - output: json.output ? new Output(json.output) : undefined, - prevTxId: json.prevTxId || json.txidbuf, - outputIndex: _.isUndefined(json.outputIndex) ? json.txoutnum : json.outputIndex, - sequenceNumber: json.sequenceNumber || json.seqnum, - scriptBuffer: new Script(json.script, 'hex') - }); + $.checkArgument(JSUtil.isValidJSON(json), 'Invalid JSON provided to Input.fromJSON'); + return Input.fromObject(JSON.parse(json)); }; Input.fromBufferReader = function(br) { @@ -107,7 +112,7 @@ Input.prototype.setScript = function(script) { this._script = null; this._scriptBuffer = new buffer.Buffer(script); } else { - throw new TypeError('Invalid Argument'); + throw new TypeError('Invalid argument type: script'); } return this; }; @@ -163,9 +168,7 @@ Input.prototype.isNull = function() { }; Input.prototype._estimateSize = function() { - var bufferWriter = new BufferWriter(); - this.toBufferWriter(bufferWriter); - return bufferWriter.toBuffer().length; + return this.toBufferWriter().toBuffer().length; }; module.exports = Input; diff --git a/lib/transaction/output.js b/lib/transaction/output.js index 767941a37..b5493c608 100644 --- a/lib/transaction/output.js +++ b/lib/transaction/output.js @@ -77,7 +77,7 @@ Output.fromJSON = function(json) { json = JSON.parse(json); } return new Output({ - satoshis: json.satoshis || -(-json.valuebn), + satoshis: json.satoshis || +json.valuebn, script: new Script(json.script) }); }; @@ -93,7 +93,7 @@ Output.prototype.setScript = function(script) { this._scriptBuffer = script; this._script = null; } else { - throw new TypeError('Unrecognized Argument'); + throw new TypeError('Invalid argument type: script'); } return this; }; diff --git a/lib/transaction/transaction.js b/lib/transaction/transaction.js index 80a964c2d..ca24533cd 100644 --- a/lib/transaction/transaction.js +++ b/lib/transaction/transaction.js @@ -60,7 +60,6 @@ function Transaction(serialized) { var CURRENT_VERSION = 1; var DEFAULT_NLOCKTIME = 0; -var DEFAULT_SEQNUMBER = 0xFFFFFFFF; // Minimum amount for an output for it not to be considered a dust output Transaction.DUST_AMOUNT = 546; @@ -516,7 +515,6 @@ Transaction.prototype._fromNonP2SH = function(utxo) { }), prevTxId: utxo.txId, outputIndex: utxo.outputIndex, - sequenceNumber: DEFAULT_SEQNUMBER, script: Script.empty() })); }; @@ -532,7 +530,6 @@ Transaction.prototype._fromMultisigUtxo = function(utxo, pubkeys, threshold) { }), prevTxId: utxo.txId, outputIndex: utxo.outputIndex, - sequenceNumber: DEFAULT_SEQNUMBER, script: Script.empty() }, pubkeys, threshold)); }; diff --git a/test/transaction/input/input.js b/test/transaction/input/input.js index 7b65129f5..6f1bfc09d 100644 --- a/test/transaction/input/input.js +++ b/test/transaction/input/input.js @@ -30,6 +30,14 @@ describe('Transaction.Input', function() { script: new Script(), satoshis: 1000000 }; + var coinbaseJSON = '{"prevTxId":"0000000000000000000000000000000000000000000000000000000000000000"' + + ',"outputIndex":4294967295,"script":""}'; + var otherJSON = '{"txidbuf":"a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458"' + + ',"txoutnum":0,"seqnum":4294967295,"script":"71 0x3044022006553276ec5b885ddf5cc1d7' + + '9e1e3dadbb404b60ad4cc00318e215654f13242102200757c17b36e3d0492fb9cf597032e5afbea67a59274e64af' + + '5a05d12e5ea2303901 33 0x0223078d2942df62c45621d209fab84ea9a7a23346201b7727b9b45a29c4e76f5e",' + + '"output":{"satoshis":100000,"script":"OP_DUP OP_HASH160 20 0x88d9931ea73d60eaf7e5671efc0552b' + + '912911f2a OP_EQUALVERIFY OP_CHECKSIG"}}'; it('has abstract methods: "getSignatures", "isFullySigned", "addSignature", "clearSignatures"', function() { var input = new Input(output); @@ -41,6 +49,39 @@ describe('Transaction.Input', function() { }); it('detects coinbase transactions', function() { new Input(output).isNull().should.equal(false); - new Input(coinbase).isNull().should.equal(true); + var ci = new Input(coinbase); + ci.isNull().should.equal(true); + }); + + describe('instantiation', function() { + it('works without new', function() { + var input = Input(); + should.exist(input); + }); + it('fails with no script info', function() { + expect(function() { + var input = new Input({}); + input.toString(); + }).to.throw('Need a script to create an input'); + }); + it('fromJSON', function() { + var input = Input.fromJSON(coinbaseJSON); + var otherInput = Input.fromJSON(otherJSON); + should.exist(input); + should.exist(otherInput); + }); + it('fromObject', function() { + var input = Input.fromJSON(coinbaseJSON); + var obj = input.toObject(); + obj.script = new Buffer(obj.script, 'hex'); + Input.fromObject(obj).should.deep.equal(input); + obj.script = 42; + Input.fromObject.bind(null, obj).should.throw('Invalid argument type: script'); + }); + }); + + it('_estimateSize', function() { + var input = new Input(output); + input._estimateSize().should.equal(66); }); }); diff --git a/test/transaction/output.js b/test/transaction/output.js index 529233c06..6d8a7d2d1 100644 --- a/test/transaction/output.js +++ b/test/transaction/output.js @@ -17,10 +17,16 @@ var errors = bitcore.errors; describe('Output', function() { - var output = new Output({satoshis: 0, script: Script.empty()}); + var output = new Output({ + satoshis: 0, + script: Script.empty() + }); it('can be assigned a satoshi amount in big number', function() { - var newOutput = new Output({satoshis: new BN(100), script: Script.empty()}); + var newOutput = new Output({ + satoshis: new BN(100), + script: Script.empty() + }); newOutput.satoshis.should.equal(100); }); @@ -37,7 +43,10 @@ describe('Output', function() { }); it('roundtrips to/from object', function() { - var newOutput = new Output({satoshis: 50, script: new Script().add(0)}); + var newOutput = new Output({ + satoshis: 50, + script: new Script().add(0) + }); var otherOutput = new Output(newOutput.toObject()); expectEqualOutputs(newOutput, otherOutput); }); @@ -47,8 +56,30 @@ describe('Output', function() { newOutput.setScript(Script().add(0).toBuffer()); newOutput.inspect().should.equal('>'); }); - + it('has a inspect property', function() { output.inspect().should.equal('>'); }); + + var output2 = new Output({ + satoshis: 1100000000, + script: new Script('OP_2 21 0x038282263212c609d9ea2a6e3e172de238d8c39' + + 'cabd5ac1ca10646e23fd5f51508 21 0x038282263212c609d9ea2a6e3e172de23' + + '8d8c39cabd5ac1ca10646e23fd5f51508 OP_2 OP_CHECKMULTISIG OP_EQUAL') + }); + it('toBufferWriter', function() { + output2.toBufferWriter().toBuffer().toString('hex') + .should.equal('00ab904100000000485215038282263212c609d9ea2a6e3e172de2' + + '38d8c39cabd5ac1ca10646e23fd5f5150815038282263212c609d9ea2a6e3e172d' + + 'e238d8c39cabd5ac1ca10646e23fd5f5150852ae87'); + }); + it('to/from JSON', function() { + var json = output2.toJSON(); + var o3 = new Output(json); + o3.toJSON().should.equal(json); + }); + it('setScript fails with invalid input', function() { + var out = new Output(output2.toJSON()); + out.setScript.bind(out, 45).should.throw('Invalid argument type: script'); + }); }); From b685b5d28a176d56381c59cf29c8fff501647629 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Mon, 2 Mar 2015 10:44:39 -0300 Subject: [PATCH 2/2] add inputAmount and outputAmount to docs --- docs/transaction.md | 6 ++++-- test/transaction/input/input.js | 6 +++--- test/transaction/output.js | 24 ++++++++++++++---------- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/docs/transaction.md b/docs/transaction.md index 9dfbe4794..24d26a768 100644 --- a/docs/transaction.md +++ b/docs/transaction.md @@ -24,6 +24,8 @@ var transaction = new Transaction() .sign(privkeySet) // Signs all the inputs it can ``` +You can obtain the input and output total amounts of the transaction in satoshis by accessing the fields `inputAmount` and `outputAmount`. + Now, this could just be serialized to hexadecimal ASCII values (`transaction.serialize()`) and sent over to the bitcoind reference client. ```bash @@ -102,7 +104,7 @@ Some methods related to adding inputs are: - `from(utxos)`: same as above, but passing in an array of Unspent Outputs. - `from(utxo, publicKeys, threshold)`: add an input that spends a UTXO with a P2SH output for a Multisig script. The `publicKeys` argument is an array of public keys, and `threshold` is the number of required signatures in the Multisig script. * `addInput`: Performs a series of checks on an input and appends it to the end of the `input` vector and updates the amount of incoming bitcoins of the transaction. -* `uncheckedAddInput`: adds an input to the end of the `input` vector and updates the `_inputAmount` without performing any checks. +* `uncheckedAddInput`: adds an input to the end of the `input` vector and updates the `inputAmount` without performing any checks. ### PublicKeyHashInput @@ -131,7 +133,7 @@ The following methods are used to manage signatures for a transaction: Outputs can be added by: -* The `addOutput(output)` method, which pushes an `Output` to the end of the `outputs` property and updates the `_outputAmount`. It also clears signatures (as the hash of the transaction may have changed) and updates the change output. +* The `addOutput(output)` method, which pushes an `Output` to the end of the `outputs` property and updates the `outputAmount` field. It also clears signatures (as the hash of the transaction may have changed) and updates the change output. * The `to(address, amount)` method, that adds an output with the script that corresponds to the given address. Builds an output and calls the `addOutput` method. * Specifying a [change address](#Fee_calculation) diff --git a/test/transaction/input/input.js b/test/transaction/input/input.js index 6f1bfc09d..d4324a988 100644 --- a/test/transaction/input/input.js +++ b/test/transaction/input/input.js @@ -64,13 +64,13 @@ describe('Transaction.Input', function() { input.toString(); }).to.throw('Need a script to create an input'); }); - it('fromJSON', function() { + it('fromJSON should work', function() { var input = Input.fromJSON(coinbaseJSON); var otherInput = Input.fromJSON(otherJSON); should.exist(input); should.exist(otherInput); }); - it('fromObject', function() { + it('fromObject should work', function() { var input = Input.fromJSON(coinbaseJSON); var obj = input.toObject(); obj.script = new Buffer(obj.script, 'hex'); @@ -80,7 +80,7 @@ describe('Transaction.Input', function() { }); }); - it('_estimateSize', function() { + it('_estimateSize returns correct size', function() { var input = new Input(output); input._estimateSize().should.equal(66); }); diff --git a/test/transaction/output.js b/test/transaction/output.js index 6d8a7d2d1..93a35c147 100644 --- a/test/transaction/output.js +++ b/test/transaction/output.js @@ -42,15 +42,6 @@ describe('Output', function() { expectEqualOutputs(output, deserialized); }); - it('roundtrips to/from object', function() { - var newOutput = new Output({ - satoshis: 50, - script: new Script().add(0) - }); - var otherOutput = new Output(newOutput.toObject()); - expectEqualOutputs(newOutput, otherOutput); - }); - it('can set a script from a buffer', function() { var newOutput = Output(output); newOutput.setScript(Script().add(0).toBuffer()); @@ -67,19 +58,32 @@ describe('Output', function() { 'cabd5ac1ca10646e23fd5f51508 21 0x038282263212c609d9ea2a6e3e172de23' + '8d8c39cabd5ac1ca10646e23fd5f51508 OP_2 OP_CHECKMULTISIG OP_EQUAL') }); + it('toBufferWriter', function() { output2.toBufferWriter().toBuffer().toString('hex') .should.equal('00ab904100000000485215038282263212c609d9ea2a6e3e172de2' + '38d8c39cabd5ac1ca10646e23fd5f5150815038282263212c609d9ea2a6e3e172d' + 'e238d8c39cabd5ac1ca10646e23fd5f5150852ae87'); }); - it('to/from JSON', function() { + + it('roundtrips to/from object', function() { + var newOutput = new Output({ + satoshis: 50, + script: new Script().add(0) + }); + var otherOutput = new Output(newOutput.toObject()); + expectEqualOutputs(newOutput, otherOutput); + }); + + it('roundtrips to/from JSON', function() { var json = output2.toJSON(); var o3 = new Output(json); o3.toJSON().should.equal(json); }); + it('setScript fails with invalid input', function() { var out = new Output(output2.toJSON()); out.setScript.bind(out, 45).should.throw('Invalid argument type: script'); }); + });