I removed the skip over the tx_valid.json file and made some tweaks to get most of the test cases passing. There are still two test cases that fail, as pointed out by the TODO comment I added above them. Oddly, running the test suite reports 3 failing test cases, but if I delete the two marked with the TODO there are 0 reported failures. So, there may be some kind of interaction with these test cases and the others. More investigation is needed.
I updated the two test cases that were testing transaction `23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63` with the input script I found on blockchain.info https://blockchain.info/tx/23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63. A quick search found one other person who was using this same script (23b397edcc.json) and the test passes now, so I'm not sure why the old script was being used.
All of the other changes are simply re-formatting decimal numbers as hex (i.e. `1` => `0x01`).
Furthermore, I added some code in the test fixture itself to verify each of the inputs.
Test Plan:
`mocha -R spec test/test.Transaction.js`
304 lines
11 KiB
JavaScript
304 lines
11 KiB
JavaScript
'use strict';
|
|
|
|
var chai = chai || require('chai');
|
|
var bitcore = bitcore || require('../bitcore');
|
|
|
|
var should = chai.should();
|
|
|
|
var TransactionModule = bitcore.Transaction;
|
|
var Transaction;
|
|
var In;
|
|
var Out;
|
|
var Script = bitcore.Script;
|
|
var util = bitcore.util;
|
|
var buffertools = require('buffertools');
|
|
var testdata = testdata || require('./testdata');
|
|
|
|
describe('Transaction', function() {
|
|
it('should initialze the main object', function() {
|
|
should.exist(TransactionModule);
|
|
});
|
|
it('should be able to create class', function() {
|
|
Transaction = TransactionModule;
|
|
should.exist(Transaction);
|
|
In = Transaction.In;
|
|
Out = Transaction.Out;
|
|
should.exist(In);
|
|
should.exist(Out);
|
|
});
|
|
|
|
|
|
it('should be able to create instance', function() {
|
|
var t = new Transaction();
|
|
should.exist(t);
|
|
});
|
|
|
|
|
|
it('#selectUnspent should be able to select utxos', function() {
|
|
var u = Transaction.selectUnspent(testdata.dataUnspent,1.0, true);
|
|
u.length.should.equal(3);
|
|
|
|
should.exist(u[0].amount);
|
|
should.exist(u[0].txid);
|
|
should.exist(u[0].scriptPubKey);
|
|
should.exist(u[0].vout);
|
|
|
|
u = Transaction.selectUnspent(testdata.dataUnspent,0.5, true);
|
|
u.length.should.equal(3);
|
|
|
|
u = Transaction.selectUnspent(testdata.dataUnspent,0.1, true);
|
|
u.length.should.equal(2);
|
|
|
|
u = Transaction.selectUnspent(testdata.dataUnspent,0.05, true);
|
|
u.length.should.equal(2);
|
|
|
|
u = Transaction.selectUnspent(testdata.dataUnspent,0.015, true);
|
|
u.length.should.equal(2);
|
|
|
|
u = Transaction.selectUnspent(testdata.dataUnspent,0.01, true);
|
|
u.length.should.equal(1);
|
|
});
|
|
|
|
it('#selectUnspent should return null if not enough utxos', function() {
|
|
var u = Transaction.selectUnspent(testdata.dataUnspent,1.12);
|
|
should.not.exist(u);
|
|
});
|
|
|
|
|
|
it('#selectUnspent should check confirmations', function() {
|
|
var u = Transaction.selectUnspent(testdata.dataUnspent,0.9);
|
|
should.not.exist(u);
|
|
var u = Transaction.selectUnspent(testdata.dataUnspent,0.9,true);
|
|
u.length.should.equal(3);
|
|
|
|
var u = Transaction.selectUnspent(testdata.dataUnspent,0.11);
|
|
u.length.should.equal(2);
|
|
var u = Transaction.selectUnspent(testdata.dataUnspent,0.111);
|
|
should.not.exist(u);
|
|
});
|
|
|
|
|
|
var opts = {
|
|
remainderAddress: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd',
|
|
allowUnconfirmed: true,
|
|
};
|
|
|
|
it('#create should be able to create instance', function() {
|
|
var utxos =testdata.dataUnspent;
|
|
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
|
|
|
var ret = Transaction.create(utxos, outs, opts);
|
|
should.exist(ret.tx);
|
|
should.exist(ret.selectedUtxos);
|
|
ret.selectedUtxos.length.should.equal(2);
|
|
|
|
var tx = ret.tx;
|
|
|
|
tx.version.should.equal(1);
|
|
tx.ins.length.should.equal(2);
|
|
tx.outs.length.should.equal(2);
|
|
|
|
util.valueToBigInt(tx.outs[0].v).cmp(8000000).should.equal(0);
|
|
|
|
// remainder is 0.0299 here because unspent select utxos in order
|
|
util.valueToBigInt(tx.outs[1].v).cmp(2990000).should.equal(0);
|
|
tx.isComplete().should.equal(false);
|
|
});
|
|
|
|
it('#create should fail if not enough inputs ', function() {
|
|
var utxos =testdata.dataUnspent;
|
|
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:80}];
|
|
Transaction
|
|
.create
|
|
.bind(utxos, outs, opts)
|
|
.should.throw();
|
|
|
|
var outs2 = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.5}];
|
|
should.exist( Transaction.create(utxos, outs2, opts));
|
|
|
|
// do not allow unconfirmed
|
|
Transaction.create.bind(utxos, outs2).should.throw();
|
|
});
|
|
|
|
|
|
it('#create should create same output as bitcoind createrawtransaction ', function() {
|
|
var utxos =testdata.dataUnspent;
|
|
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
|
var ret = Transaction.create(utxos, outs, opts);
|
|
var tx = ret.tx;
|
|
|
|
// string output generated from: bitcoind createrawtransaction '[{"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1","vout":1},{"txid":"2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc2","vout":0} ]' '{"mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE":0.08,"mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd":0.0299}'
|
|
tx.serialize().toString('hex').should.equal('0100000002c1cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0100000000ffffffffc2cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0000000000ffffffff0200127a00000000001976a914774e603bafb717bd3f070e68bbcccfd907c77d1388acb09f2d00000000001976a914b00127584485a7cff0949ef0f6bc5575f06ce00d88ac00000000');
|
|
|
|
});
|
|
|
|
it('#create should create same output as bitcoind createrawtransaction wo remainder', function() {
|
|
var utxos =testdata.dataUnspent;
|
|
// no remainder
|
|
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
|
var ret = Transaction.create(utxos, outs, {fee:0.03} );
|
|
var tx = ret.tx;
|
|
|
|
// string output generated from: bitcoind createrawtransaction '[{"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1","vout":1},{"txid":"2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc2","vout":0} ]' '{"mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE":0.08}'
|
|
//
|
|
tx.serialize().toString('hex').should.equal('0100000002c1cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0100000000ffffffffc2cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0000000000ffffffff0100127a00000000001976a914774e603bafb717bd3f070e68bbcccfd907c77d1388ac00000000');
|
|
});
|
|
|
|
it('#createAndSign should sign a tx', function() {
|
|
var utxos =testdata.dataUnspentSign.unspent;
|
|
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
|
var ret = Transaction.createAndSign(utxos, outs, testdata.dataUnspentSign.keyStrings, opts);
|
|
var tx = ret.tx;
|
|
tx.isComplete().should.equal(true);
|
|
tx.ins.length.should.equal(1);
|
|
tx.outs.length.should.equal(2);
|
|
|
|
var outs2 = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:16}];
|
|
var ret2 = Transaction.createAndSign(utxos, outs2, testdata.dataUnspentSign.keyStrings, opts);
|
|
var tx2 = ret2.tx;
|
|
tx2.isComplete().should.equal(true);
|
|
tx2.ins.length.should.equal(3);
|
|
tx2.outs.length.should.equal(2);
|
|
});
|
|
|
|
it('#createAndSign should sign an incomplete tx ', function() {
|
|
var keys = ['cNpW8B7XPAzCdRR9RBWxZeveSNy3meXgHD8GuhcqUyDuy8ptCDzJ'];
|
|
var utxos =testdata.dataUnspentSign.unspent;
|
|
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
|
var ret = Transaction.createAndSign(utxos, outs, keys, opts);
|
|
var tx = ret.tx;
|
|
tx.ins.length.should.equal(1);
|
|
tx.outs.length.should.equal(2);
|
|
});
|
|
it('#isComplete should return TX signature status', function() {
|
|
var keys = ['cNpW8B7XPAzCdRR9RBWxZeveSNy3meXgHD8GuhcqUyDuy8ptCDzJ'];
|
|
var utxos =testdata.dataUnspentSign.unspent;
|
|
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
|
var ret = Transaction.createAndSign(utxos, outs, keys, opts);
|
|
var tx = ret.tx;
|
|
tx.isComplete().should.equal(false);
|
|
tx.sign(ret.selectedUtxos, testdata.dataUnspentSign.keyStrings);
|
|
tx.isComplete().should.equal(true);
|
|
});
|
|
|
|
it('#sign should sign a tx in multiple steps (case1)', function() {
|
|
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:1.08}];
|
|
var ret = Transaction.create(testdata.dataUnspentSign.unspent, outs, opts);
|
|
var tx = ret.tx;
|
|
var selectedUtxos = ret.selectedUtxos;
|
|
|
|
var k1 = testdata.dataUnspentSign.keyStrings.slice(0,1);
|
|
|
|
tx.isComplete().should.equal(false);
|
|
|
|
tx.sign(selectedUtxos, k1).should.equal(false);
|
|
|
|
var k23 = testdata.dataUnspentSign.keyStrings.slice(1,3);
|
|
tx.sign(selectedUtxos, k23).should.equal(true);
|
|
tx.isComplete().should.equal(true);
|
|
});
|
|
|
|
it('#sign should sign a tx in multiple steps (case2)', function() {
|
|
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:16}];
|
|
var ret = Transaction.create(testdata.dataUnspentSign.unspent, outs, opts);
|
|
var tx = ret.tx;
|
|
var selectedUtxos = ret.selectedUtxos;
|
|
|
|
var k1 = testdata.dataUnspentSign.keyStrings.slice(0,1);
|
|
var k2 = testdata.dataUnspentSign.keyStrings.slice(1,2);
|
|
var k3 = testdata.dataUnspentSign.keyStrings.slice(2,3);
|
|
tx.sign(selectedUtxos, k1).should.equal(false);
|
|
tx.sign(selectedUtxos, k2).should.equal(false);
|
|
tx.sign(selectedUtxos, k3).should.equal(true);
|
|
|
|
});
|
|
|
|
it('#createAndSign: should generate dynamic fee and readjust (and not) the selected UTXOs', function() {
|
|
//this cases exceeds the input by 1mbtc AFTEr calculating the dynamic fee,
|
|
//so, it should trigger adding a new 10BTC utxo
|
|
var utxos =testdata.dataUnspentSign.unspent;
|
|
var outs = [];
|
|
var n =101;
|
|
for (var i=0; i<n; i++) {
|
|
outs.push({address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.01});
|
|
}
|
|
|
|
var ret = Transaction.createAndSign(utxos, outs, testdata.dataUnspentSign.keyStrings, opts);
|
|
var tx = ret.tx;
|
|
tx.getSize().should.equal(3560);
|
|
|
|
// ins = 11.0101 BTC (2 inputs: 1.0101 + 10 );
|
|
tx.ins.length.should.equal(2);
|
|
// outs = 101 outs:
|
|
// 101 * 0.01 = 1.01BTC; + 0.0004 fee = 1.0104btc
|
|
// remainder = 11.0101-1.0104 = 9.9997
|
|
tx.outs.length.should.equal(102);
|
|
util.valueToBigInt(tx.outs[n].v).cmp(999970000).should.equal(0);
|
|
tx.isComplete().should.equal(true);
|
|
|
|
|
|
//this is the complementary case, it does not trigger a new utxo
|
|
var utxos =testdata.dataUnspentSign.unspent;
|
|
var outs = [];
|
|
var n =100;
|
|
for (var i=0; i<n; i++) {
|
|
outs.push({address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.01});
|
|
}
|
|
|
|
var ret = Transaction.createAndSign(utxos, outs, testdata.dataUnspentSign.keyStrings, opts);
|
|
var tx = ret.tx;
|
|
tx.getSize().should.equal(3485);
|
|
|
|
// ins = 1.0101 BTC (1 inputs: 1.0101);
|
|
tx.ins.length.should.equal(1);
|
|
// outs = 100 outs:
|
|
// 100 * 0.01 = 1BTC; + 0.0004 fee = 1.0004btc
|
|
// remainder = 1.0101-1.0004 = 0.0097
|
|
tx.outs.length.should.equal(101);
|
|
util.valueToBigInt(tx.outs[n].v).cmp(970000).should.equal(0);
|
|
tx.isComplete().should.equal(true);
|
|
});
|
|
|
|
|
|
// Read tests from test/data/tx_valid.json
|
|
// Format is an array of arrays
|
|
// Inner arrays are either [ "comment" ]
|
|
// or [[[prevout hash, prevout index, prevout scriptPubKey], [input 2], ...],"], serializedTransaction, enforceP2SH
|
|
// ... where all scripts are stringified scripts.
|
|
testdata.dataTxValid.forEach(function(datum) {
|
|
if (datum.length === 3) {
|
|
it('valid tx=' + datum[1], function() {
|
|
var inputs = datum[0];
|
|
var inputScriptPubKeys = [];
|
|
inputs.forEach(function(vin) {
|
|
var hash = vin[0];
|
|
var index = vin[1];
|
|
var scriptPubKey = new Script(new Buffer(vin[2]));
|
|
inputScriptPubKeys.push(scriptPubKey);
|
|
console.log(scriptPubKey.getStringContent());
|
|
console.log('********************************');
|
|
done();
|
|
|
|
});
|
|
var raw = new Buffer(datum[1], 'hex');
|
|
var tx = new Transaction();
|
|
tx.parse(raw);
|
|
|
|
buffertools.toHex(tx.serialize()).should.equal(buffertools.toHex(raw));
|
|
|
|
var n = 0;
|
|
inputScriptPubKeys.forEach(function(scriptPubKey) {
|
|
tx.verifyInput(0, scriptPubKey, function(err, results) {
|
|
should.not.exist(err);
|
|
should.exist(results);
|
|
results.should.equal(true);
|
|
});
|
|
n += 1;
|
|
});
|
|
|
|
// TODO(mattfaus): Other verifications?
|
|
});
|
|
}
|
|
});
|
|
});
|