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:
parent
0dbd9db0ea
commit
458abe069e
|
@ -25,7 +25,7 @@
|
||||||
|
|
||||||
"maxparams": 4, // Maximum number of parameters for a function
|
"maxparams": 4, // Maximum number of parameters for a function
|
||||||
"maxstatements": 15, // Maximum number of statements in 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
|
"maxdepth": 4, // Maximum depth of nested control structures
|
||||||
"maxlen": 120, // Maximum number of cols in a line
|
"maxlen": 120, // Maximum number of cols in a line
|
||||||
"multistr": true, // Allow use of multiline EOL escaping
|
"multistr": true, // Allow use of multiline EOL escaping
|
||||||
|
|
|
@ -119,7 +119,7 @@ Input.prototype.setScript = function(script) {
|
||||||
this._scriptBuffer = script.toBuffer();
|
this._scriptBuffer = script.toBuffer();
|
||||||
} else if (JSUtil.isHexa(script)) {
|
} else if (JSUtil.isHexa(script)) {
|
||||||
// hex string script
|
// hex string script
|
||||||
this._scriptBuffer = new Buffer(script, 'hex');
|
this._scriptBuffer = new buffer.Buffer(script, 'hex');
|
||||||
} else if (_.isString(script)) {
|
} else if (_.isString(script)) {
|
||||||
// human readable string script
|
// human readable string script
|
||||||
this._script = new Script(script);
|
this._script = new Script(script);
|
||||||
|
|
|
@ -12,15 +12,20 @@ var errors = require('../errors');
|
||||||
|
|
||||||
var MAX_SAFE_INTEGER = 0x1fffffffffffff;
|
var MAX_SAFE_INTEGER = 0x1fffffffffffff;
|
||||||
|
|
||||||
function Output(params) {
|
function Output(args) {
|
||||||
if (!(this instanceof Output)) {
|
if (!(this instanceof Output)) {
|
||||||
return new Output(params);
|
return new Output(args);
|
||||||
}
|
}
|
||||||
if (params) {
|
if (JSUtil.isValidJSON(args)) {
|
||||||
if (JSUtil.isValidJSON(params)) {
|
return Output.fromJSON(args);
|
||||||
return Output.fromJSON(params);
|
} 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,
|
configurable: false,
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
get: function() {
|
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;
|
return this._script;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -84,45 +78,49 @@ Output.prototype.invalidSatoshis = function() {
|
||||||
return false;
|
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() {
|
Output.prototype.toObject = function toObject() {
|
||||||
return {
|
var obj = {
|
||||||
satoshis: this.satoshis,
|
satoshis: this.satoshis
|
||||||
script: this.script.toString()
|
|
||||||
};
|
};
|
||||||
|
obj.script = this._scriptBuffer.toString('hex');
|
||||||
|
return obj;
|
||||||
};
|
};
|
||||||
|
|
||||||
Output.prototype.toJSON = function toJSON() {
|
Output.prototype.toJSON = function toJSON() {
|
||||||
return JSON.stringify(this.toObject());
|
return JSON.stringify(this.toObject());
|
||||||
};
|
};
|
||||||
|
|
||||||
Output.fromJSON = function(json) {
|
Output.fromJSON = function(data) {
|
||||||
if (JSUtil.isValidJSON(json)) {
|
$.checkArgument(JSUtil.isValidJSON(data), 'data must be valid JSON');
|
||||||
json = JSON.parse(json);
|
var json = JSON.parse(data);
|
||||||
}
|
|
||||||
return new Output({
|
return new Output({
|
||||||
satoshis: json.satoshis || +json.valuebn,
|
satoshis: Number(json.satoshis),
|
||||||
script: new Script(json.script)
|
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) {
|
Output.prototype.setScript = function(script) {
|
||||||
if (script instanceof Script) {
|
if (script instanceof Script) {
|
||||||
this._scriptBuffer = script.toBuffer();
|
this._scriptBuffer = script.toBuffer();
|
||||||
this._script = script;
|
this._script = script;
|
||||||
} else if (_.isString(script)) {
|
} else if (_.isString(script)) {
|
||||||
this._script = new Script(script);
|
this._script = Script.fromString(script);
|
||||||
this._scriptBuffer = this._script.toBuffer();
|
this._scriptBuffer = this._script.toBuffer();
|
||||||
} else if (bufferUtil.isBuffer(script)) {
|
} else if (bufferUtil.isBuffer(script)) {
|
||||||
this._scriptBuffer = script;
|
this.setScriptFromBuffer(script);
|
||||||
this._script = null;
|
|
||||||
} else {
|
} else {
|
||||||
throw new TypeError('Invalid argument type: script');
|
throw new TypeError('Invalid argument type: script');
|
||||||
}
|
}
|
||||||
|
@ -130,19 +128,25 @@ Output.prototype.setScript = function(script) {
|
||||||
};
|
};
|
||||||
|
|
||||||
Output.prototype.inspect = function() {
|
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) {
|
Output.fromBufferReader = function(br) {
|
||||||
var output = new Output();
|
var obj = {};
|
||||||
output.satoshis = br.readUInt64LEBN();
|
obj.satoshis = br.readUInt64LEBN();
|
||||||
var size = br.readVarintNum();
|
var size = br.readVarintNum();
|
||||||
if (size !== 0) {
|
if (size !== 0) {
|
||||||
output._scriptBuffer = br.read(size);
|
obj.script = br.read(size);
|
||||||
} else {
|
} else {
|
||||||
output._scriptBuffer = new buffer.Buffer([]);
|
obj.script = new buffer.Buffer([]);
|
||||||
}
|
}
|
||||||
return output;
|
return new Output(obj);
|
||||||
};
|
};
|
||||||
|
|
||||||
Output.prototype.toBufferWriter = function(writer) {
|
Output.prototype.toBufferWriter = function(writer) {
|
||||||
|
|
|
@ -192,8 +192,8 @@ describe('Block', function() {
|
||||||
}],
|
}],
|
||||||
outputs: [{
|
outputs: [{
|
||||||
satoshis: 5000000000,
|
satoshis: 5000000000,
|
||||||
script: '65 0x0496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be' +
|
script: '410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c' +
|
||||||
'63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858ee OP_CHECKSIG'
|
'52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac'
|
||||||
}],
|
}],
|
||||||
nLockTime: 0
|
nLockTime: 0
|
||||||
}]
|
}]
|
||||||
|
|
|
@ -30,14 +30,26 @@ describe('Transaction.Input', function() {
|
||||||
script: new Script(),
|
script: new Script(),
|
||||||
satoshis: 1000000
|
satoshis: 1000000
|
||||||
};
|
};
|
||||||
var coinbaseJSON = '{"prevTxId":"0000000000000000000000000000000000000000000000000000000000000000"' +
|
|
||||||
',"outputIndex":4294967295,"script":""}';
|
var coinbaseJSON = JSON.stringify({
|
||||||
var otherJSON = '{"txidbuf":"a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458"' +
|
prevTxId: '0000000000000000000000000000000000000000000000000000000000000000',
|
||||||
',"txoutnum":0,"seqnum":4294967295,"script":"71 0x3044022006553276ec5b885ddf5cc1d7' +
|
outputIndex: 4294967295,
|
||||||
'9e1e3dadbb404b60ad4cc00318e215654f13242102200757c17b36e3d0492fb9cf597032e5afbea67a59274e64af' +
|
script:''
|
||||||
'5a05d12e5ea2303901 33 0x0223078d2942df62c45621d209fab84ea9a7a23346201b7727b9b45a29c4e76f5e",' +
|
});
|
||||||
'"output":{"satoshis":100000,"script":"OP_DUP OP_HASH160 20 0x88d9931ea73d60eaf7e5671efc0552b' +
|
|
||||||
'912911f2a OP_EQUALVERIFY OP_CHECKSIG"}}';
|
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() {
|
it('has abstract methods: "getSignatures", "isFullySigned", "addSignature", "clearSignatures"', function() {
|
||||||
var input = new Input(output);
|
var input = new Input(output);
|
||||||
|
|
|
@ -22,6 +22,12 @@ describe('Output', function() {
|
||||||
script: Script.empty()
|
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() {
|
it('can be assigned a satoshi amount in big number', function() {
|
||||||
var newOutput = new Output({
|
var newOutput = new Output({
|
||||||
satoshis: new BN(100),
|
satoshis: new BN(100),
|
||||||
|
@ -88,8 +94,13 @@ describe('Output', function() {
|
||||||
expectEqualOutputs(output, deserialized);
|
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() {
|
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.setScript(Script().add(0).toBuffer());
|
||||||
newOutput.inspect().should.equal('<Output (0 sats) <Script: OP_0>>');
|
newOutput.inspect().should.equal('<Output (0 sats) <Script: OP_0>>');
|
||||||
});
|
});
|
||||||
|
@ -121,6 +132,34 @@ describe('Output', function() {
|
||||||
expectEqualOutputs(newOutput, otherOutput);
|
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() {
|
it('roundtrips to/from JSON', function() {
|
||||||
var json = output2.toJSON();
|
var json = output2.toJSON();
|
||||||
var o3 = new Output(json);
|
var o3 = new Output(json);
|
||||||
|
@ -134,22 +173,20 @@ describe('Output', function() {
|
||||||
|
|
||||||
it('sets script to null if it is an InvalidBuffer', function() {
|
it('sets script to null if it is an InvalidBuffer', function() {
|
||||||
var output = new Output({
|
var output = new Output({
|
||||||
satoshis: 1000
|
satoshis: 1000,
|
||||||
|
script: new Buffer('4c', 'hex')
|
||||||
});
|
});
|
||||||
output._scriptBuffer = new Buffer('4c', 'hex');
|
should.equal(output.script, null);
|
||||||
|
|
||||||
var result = output.script;
|
|
||||||
should.equal(result, null);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if Script throws an error that is not InvalidBuffer', function() {
|
it('should throw an error if Script throws an error that is not InvalidBuffer', function() {
|
||||||
var output = new Output({
|
var output = Output({
|
||||||
satoshis: 1000
|
satoshis: 1000,
|
||||||
|
script: new Script()
|
||||||
});
|
});
|
||||||
output._scriptBuffer = 'bad';
|
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
var result = output.script;
|
output.setScriptFromBuffer('bad');
|
||||||
}).should.throw('Invalid hex string');
|
}).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 testScript = 'OP_DUP OP_HASH160 20 0x88d9931ea73d60eaf7e5671efc0552b912911f2a OP_EQUALVERIFY OP_CHECKSIG';
|
||||||
|
var testScriptHex = '76a91488d9931ea73d60eaf7e5671efc0552b912911f2a88ac';
|
||||||
var testPrevTx = 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458';
|
var testPrevTx = 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458';
|
||||||
var testAmount = 1020000;
|
var testAmount = 1020000;
|
||||||
var testTransaction = new Transaction()
|
var testTransaction = new Transaction()
|
||||||
|
@ -45,7 +46,7 @@ describe('Transaction', function() {
|
||||||
it('can serialize to a plain javascript object', function() {
|
it('can serialize to a plain javascript object', function() {
|
||||||
var object = testTransaction.toObject();
|
var object = testTransaction.toObject();
|
||||||
object.inputs[0].output.satoshis.should.equal(testAmount);
|
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].prevTxId.should.equal(testPrevTx);
|
||||||
object.inputs[0].outputIndex.should.equal(0);
|
object.inputs[0].outputIndex.should.equal(0);
|
||||||
object.outputs[0].satoshis.should.equal(testAmount - 10000);
|
object.outputs[0].satoshis.should.equal(testAmount - 10000);
|
||||||
|
|
Loading…
Reference in New Issue