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
This commit is contained in:
Braydon Fuller 2015-05-13 09:38:58 -04:00
parent 0dbd9db0ea
commit 458abe069e
7 changed files with 120 additions and 66 deletions

View File

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

View File

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

View File

@ -12,15 +12,20 @@ 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');
}
}
@ -28,17 +33,6 @@ 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,45 +78,49 @@ 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');
}
@ -130,19 +128,25 @@ Output.prototype.setScript = function(script) {
};
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 File

@ -192,8 +192,8 @@ describe('Block', function() {
}],
outputs: [{
satoshis: 5000000000,
script: '65 0x0496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be' +
'63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858ee OP_CHECKSIG'
script: '410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c' +
'52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac'
}],
nLockTime: 0
}]

View File

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

View File

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

View File

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