Permalink
Browse files

Fixed issue with handling invalid output scripts

- Changed toObject serialization to always use a hexa string for a script
- Updated inspect method to handle a null script
- Roundtrip toObject/fromObject with an invalid script
- Additional test coverage for Output
  • Loading branch information...
1 parent 0dbd9db commit 458abe069eaca088332d973f3a7dc7d4bd9a8bc5 Braydon Fuller committed May 13, 2015
View
@@ -25,7 +25,7 @@
"maxparams": 4, // Maximum number of parameters for a function
"maxstatements": 15, // Maximum number of statements in a function
- "maxcomplexity": 6, // Cyclomatic complexity (http://en.wikipedia.org/wiki/Cyclomatic_complexity)
+ "maxcomplexity": 10, // Cyclomatic complexity (http://en.wikipedia.org/wiki/Cyclomatic_complexity)
"maxdepth": 4, // Maximum depth of nested control structures
"maxlen": 120, // Maximum number of cols in a line
"multistr": true, // Allow use of multiline EOL escaping
@@ -119,7 +119,7 @@ Input.prototype.setScript = function(script) {
this._scriptBuffer = script.toBuffer();
} else if (JSUtil.isHexa(script)) {
// hex string script
- this._scriptBuffer = new Buffer(script, 'hex');
+ this._scriptBuffer = new buffer.Buffer(script, 'hex');
} else if (_.isString(script)) {
// human readable string script
this._script = new Script(script);
@@ -12,33 +12,27 @@ var errors = require('../errors');
var MAX_SAFE_INTEGER = 0x1fffffffffffff;
-function Output(params) {
+function Output(args) {
if (!(this instanceof Output)) {
- return new Output(params);
+ return new Output(args);
}
- if (params) {
- if (JSUtil.isValidJSON(params)) {
- return Output.fromJSON(params);
+ if (JSUtil.isValidJSON(args)) {
+ return Output.fromJSON(args);
+ } else if (_.isObject(args)) {
+ this.satoshis = args.satoshis;
+ if (_.isString(args.script) && JSUtil.isHexa(args.script)) {
+ args.script = new buffer.Buffer(args.script, 'hex');
}
- return this._fromObject(params);
+ this.setScript(args.script);
+ } else {
+ throw new TypeError('Unrecognized argument for Output');
}
}
Object.defineProperty(Output.prototype, 'script', {
configurable: false,
enumerable: true,
get: function() {
- if (!this._script) {
- try {
- this._script = Script.fromBuffer(this._scriptBuffer);
- } catch(e) {
- if(e instanceof errors.Script.InvalidBuffer) {
- this._script = null;
- } else {
- throw e;
- }
- }
- }
return this._script;
}
});
@@ -84,65 +78,75 @@ Output.prototype.invalidSatoshis = function() {
return false;
};
-Output.prototype._fromObject = function(param) {
- this.satoshis = param.satoshis;
- if (param.script || param.scriptBuffer) {
- this.setScript(param.script || param.scriptBuffer);
- }
- return this;
-};
-
Output.prototype.toObject = function toObject() {
- return {
- satoshis: this.satoshis,
- script: this.script.toString()
+ var obj = {
+ satoshis: this.satoshis
};
+ obj.script = this._scriptBuffer.toString('hex');
+ return obj;
};
Output.prototype.toJSON = function toJSON() {
return JSON.stringify(this.toObject());
};
-Output.fromJSON = function(json) {
- if (JSUtil.isValidJSON(json)) {
- json = JSON.parse(json);
- }
+Output.fromJSON = function(data) {
+ $.checkArgument(JSUtil.isValidJSON(data), 'data must be valid JSON');
+ var json = JSON.parse(data);
return new Output({
- satoshis: json.satoshis || +json.valuebn,
+ satoshis: Number(json.satoshis),
script: new Script(json.script)
});
};
+Output.prototype.setScriptFromBuffer = function(buffer) {
+ this._scriptBuffer = buffer;
+ try {
+ this._script = Script.fromBuffer(this._scriptBuffer);
+ } catch(e) {
+ if (e instanceof errors.Script.InvalidBuffer) {
+ this._script = null;
+ } else {
+ throw e;
+ }
+ }
+};
+
Output.prototype.setScript = function(script) {
if (script instanceof Script) {
this._scriptBuffer = script.toBuffer();
this._script = script;
} else if (_.isString(script)) {
- this._script = new Script(script);
+ this._script = Script.fromString(script);
this._scriptBuffer = this._script.toBuffer();
} else if (bufferUtil.isBuffer(script)) {
- this._scriptBuffer = script;
- this._script = null;
+ this.setScriptFromBuffer(script);
} else {
throw new TypeError('Invalid argument type: script');
}
return this;
};
Output.prototype.inspect = function() {
- return '<Output (' + this.satoshis + ' sats) ' + this.script.inspect() + '>';
+ var scriptStr;
+ if (this.script) {
+ scriptStr = this.script.inspect();
+ } else {
+ scriptStr = this._scriptBuffer.toString('hex');
+ }
+ return '<Output (' + this.satoshis + ' sats) ' + scriptStr + '>';
};
Output.fromBufferReader = function(br) {
- var output = new Output();
- output.satoshis = br.readUInt64LEBN();
+ var obj = {};
+ obj.satoshis = br.readUInt64LEBN();
var size = br.readVarintNum();
if (size !== 0) {
- output._scriptBuffer = br.read(size);
+ obj.script = br.read(size);
} else {
- output._scriptBuffer = new buffer.Buffer([]);
+ obj.script = new buffer.Buffer([]);
}
- return output;
+ return new Output(obj);
};
Output.prototype.toBufferWriter = function(writer) {
View
@@ -192,8 +192,8 @@ describe('Block', function() {
}],
outputs: [{
satoshis: 5000000000,
- script: '65 0x0496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be' +
- '63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858ee OP_CHECKSIG'
+ script: '410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c' +
+ '52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac'
}],
nLockTime: 0
}]
@@ -30,14 +30,26 @@ 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"}}';
+
+ var coinbaseJSON = JSON.stringify({
+ prevTxId: '0000000000000000000000000000000000000000000000000000000000000000',
+ outputIndex: 4294967295,
+ script:''
+ });
+
+ var otherJSON = JSON.stringify({
+ txidbuf: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458',
+ txoutnum: 0,
+ seqnum:4294967295,
+ script: '71 0x3044022006553276ec5b885ddf5cc1d79e1e3dadbb404b60ad4cc00318e21565' +
+ '4f13242102200757c17b36e3d0492fb9cf597032e5afbea67a59274e64af5a05d12e5ea2303901 ' +
+ '33 0x0223078d2942df62c45621d209fab84ea9a7a23346201b7727b9b45a29c4e76f5e',
+ output: {
+ 'satoshis':100000,
+ 'script':'OP_DUP OP_HASH160 20 0x88d9931ea73d60eaf7e5671efc0552b912911f2a ' +
+ 'OP_EQUALVERIFY OP_CHECKSIG'
+ }
+ });
it('has abstract methods: "getSignatures", "isFullySigned", "addSignature", "clearSignatures"', function() {
var input = new Input(output);
@@ -22,6 +22,12 @@ describe('Output', function() {
script: Script.empty()
});
+ it('throws error with unrecognized argument', function() {
+ (function() {
+ var out = new Output(12345);
+ }).should.throw(TypeError);
+ });
+
it('can be assigned a satoshi amount in big number', function() {
var newOutput = new Output({
satoshis: new BN(100),
@@ -88,8 +94,13 @@ describe('Output', function() {
expectEqualOutputs(output, deserialized);
});
+ it('can instantiate from JSON', function() {
+ var out = new Output(JSON.stringify(output.toObject()));
+ should.exist(out);
+ });
+
it('can set a script from a buffer', function() {
- var newOutput = Output(output);
+ var newOutput = new Output(output.toObject());
newOutput.setScript(Script().add(0).toBuffer());
newOutput.inspect().should.equal('<Output (0 sats) <Script: OP_0>>');
});
@@ -121,6 +132,34 @@ describe('Output', function() {
expectEqualOutputs(newOutput, otherOutput);
});
+ it('toObject will handle an invalid (null) script', function() {
+ // block 000000000000000b7e48f88e86ceee3e97b4df7c139f5411d14735c1b3c36791 (livenet)
+ // transaction index 2
+ // txid ebc9fa1196a59e192352d76c0f6e73167046b9d37b8302b6bb6968dfd279b767
+ var transaction = bitcore.Transaction();
+ transaction.fromString('01000000019ac03d5ae6a875d970128ef9086cef276a1919684a6988023cc7254691d97e6d010000006b4830450221009d41dc793ba24e65f571473d40b299b6459087cea1509f0d381740b1ac863cb6022039c425906fcaf51b2b84d8092569fb3213de43abaff2180e2a799d4fcb4dd0aa012102d5ede09a8ae667d0f855ef90325e27f6ce35bbe60a1e6e87af7f5b3c652140fdffffffff080100000000000000010101000000000000000202010100000000000000014c0100000000000000034c02010100000000000000014d0100000000000000044dffff010100000000000000014e0100000000000000064effffffff0100000000');
+ var obj = transaction.toObject();
+ obj.outputs[2].script.should.equal('4c');
+ obj.outputs[4].script.should.equal('4d');
+ obj.outputs[6].script.should.equal('4e');
+ });
+
+ it('#toObject roundtrip will handle an invalid (null) script', function() {
+ var invalidOutputScript = new Buffer('0100000000000000014c', 'hex');
+ var br = new bitcore.encoding.BufferReader(invalidOutputScript);
+ var output = Output.fromBufferReader(br);
+ var output2 = new Output(output.toObject());
+ should.equal(output2.script, null);
+ should.equal(output2._scriptBuffer.toString('hex'), '4c');
+ });
+
+ it('inspect will work with an invalid (null) script', function() {
+ var invalidOutputScript = new Buffer('0100000000000000014c', 'hex');
+ var br = new bitcore.encoding.BufferReader(invalidOutputScript);
+ var output = Output.fromBufferReader(br);
+ output.inspect().should.equal('<Output (1 sats) 4c>');
+ });
+
it('roundtrips to/from JSON', function() {
var json = output2.toJSON();
var o3 = new Output(json);
@@ -134,22 +173,20 @@ describe('Output', function() {
it('sets script to null if it is an InvalidBuffer', function() {
var output = new Output({
- satoshis: 1000
+ satoshis: 1000,
+ script: new Buffer('4c', 'hex')
});
- output._scriptBuffer = new Buffer('4c', 'hex');
-
- var result = output.script;
- should.equal(result, null);
+ should.equal(output.script, null);
});
it('should throw an error if Script throws an error that is not InvalidBuffer', function() {
- var output = new Output({
- satoshis: 1000
+ var output = Output({
+ satoshis: 1000,
+ script: new Script()
});
- output._scriptBuffer = 'bad';
-
(function() {
- var result = output.script;
+ output.setScriptFromBuffer('bad');
}).should.throw('Invalid hex string');
});
+
});
@@ -32,6 +32,7 @@ describe('Transaction', function() {
});
var testScript = 'OP_DUP OP_HASH160 20 0x88d9931ea73d60eaf7e5671efc0552b912911f2a OP_EQUALVERIFY OP_CHECKSIG';
+ var testScriptHex = '76a91488d9931ea73d60eaf7e5671efc0552b912911f2a88ac';
var testPrevTx = 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458';
var testAmount = 1020000;
var testTransaction = new Transaction()
@@ -45,7 +46,7 @@ describe('Transaction', function() {
it('can serialize to a plain javascript object', function() {
var object = testTransaction.toObject();
object.inputs[0].output.satoshis.should.equal(testAmount);
- object.inputs[0].output.script.toString().should.equal(testScript);
+ object.inputs[0].output.script.should.equal(testScriptHex);
object.inputs[0].prevTxId.should.equal(testPrevTx);
object.inputs[0].outputIndex.should.equal(0);
object.outputs[0].satoshis.should.equal(testAmount - 10000);

0 comments on commit 458abe0

Please sign in to comment.