Merge pull request #1124 from maraoz/increase/coverage

increase coverage for Input and Output, fix some bugs
This commit is contained in:
Esteban Ordano 2015-03-02 11:48:16 -03:00
commit 281ca5e481
6 changed files with 112 additions and 34 deletions

View File

@ -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)

View File

@ -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;

View File

@ -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;
};

View File

@ -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));
};

View File

@ -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 should work', function() {
var input = Input.fromJSON(coinbaseJSON);
var otherInput = Input.fromJSON(otherJSON);
should.exist(input);
should.exist(otherInput);
});
it('fromObject should work', 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 returns correct size', function() {
var input = new Input(output);
input._estimateSize().should.equal(66);
});
});

View File

@ -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);
});
@ -36,19 +42,48 @@ 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());
newOutput.inspect().should.equal('<Output (0 sats) <Script: OP_0>>');
});
it('has a inspect property', function() {
output.inspect().should.equal('<Output (0 sats) <Script: >>');
});
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('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');
});
});