#create for Transaction and tests

This commit is contained in:
Matias Alejo Garcia 2014-03-15 12:21:59 -03:00
parent 671d372c19
commit 706162e2ba
3 changed files with 115 additions and 49 deletions

View File

@ -652,6 +652,47 @@ Transaction.prototype.parse = function (parser) {
this.calcHash();
};
/*
* _selectUnspent
*
* Selects some unspend outputs for later usage in tx inputs
*
* @unspentArray: unspent array (UTXO) avaible on the form (see selectUnspent)
* @totalNeededAmount: output transaction amount in BTC, including fee
* @minConfirmations: 0 by default.
*
*
* Returns the selected outputs or null if there are not enough funds.
* The utxos are selected in the order they appear in the original array.
* Sorting must be done previusly.
*
*/
Transaction._selectUnspent = function (unspentArray, totalNeededAmount, minConfirmations) {
minConfirmations = minConfirmations || 0;
var selected = [];
var l = unspentArray.length;
var totalSat = bignum(0);
var totalNeededAmountSat = util.parseValue(totalNeededAmount);
var fullfill = false;
for(var i = 0; i<l; i++) {
var u = unspentArray[i];
if ( (u.confirmations||0) < minConfirmations)
continue;
var sat = u.amountSat || util.parseValue(u.amount);
totalSat = totalSat.add(sat);
selected.push(u);
if(totalSat.cmp(totalNeededAmountSat) >= 0) {
fullfill = true;
break;
}
}
if (!fullfill) return [];
return selected;
}
/*
* selectUnspent
*
@ -664,51 +705,31 @@ Transaction.prototype.parse = function (parser) {
* scriptPubKey: "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ac",
* vout: 1,
* amount: 0.01,
* confirmations: 3
* }, [...]
* ]
* This is compatible con insight's /utxo API.
* NOTE that amount is in BTCs! (as returned in insight and bitcoind)
* That amount is in BTCs. (as returned in insight and bitcoind)
* amountSat can be given to provide amount in satochis.
*
* @totalNeededAmount: output transaction amount in BTC, including fee
* @allowUnconfirmed:false (allow selecting unconfirmed utxos)
*
*
* Return the selected outputs or null if there are not enough funds.
* It does not check for confirmations. The unspendArray should be filtered.
* Note that the sum of the selected unspent is >= the desired amount.
*
*/
Transaction.selectUnspent = function (unspentArray, totalNeededAmount) {
// TODO implement bidcoind heristics
// A-
// 1) select utxos with 6+ confirmations
// 2) if not 2) select utxos with 1+ confirmations
// 3) if not select unconfirmed.
//
// B-
// Select smaller utxos first.
//
//
// TODO we could randomize or select the selection
var selected = [];
var l = unspentArray.length;
var totalSat = bignum(0);
var totalNeededAmountSat = util.parseValue(totalNeededAmount);
var fullfill = false;
Transaction.selectUnspent = function (unspentArray, totalNeededAmount, allowUnconfirmed) {
var answer = Transaction._selectUnspent(unspentArray, totalNeededAmount, 6);
for(var i = 0; i<l; i++) {
var u = unspentArray[i];
var sat = u.amountSat || util.parseValue(u.amount);
totalSat = totalSat.add(sat);
selected.push(u);
if(totalSat.cmp(totalNeededAmountSat) >= 0) {
fullfill = true;
break;
}
}
if (!fullfill) return [];
return selected;
if (!answer.length)
answer = Transaction._selectUnspent(unspentArray, totalNeededAmount, 1);
if (!answer.length && allowUnconfirmed)
answer = Transaction._selectUnspent(unspentArray, totalNeededAmount, 0);
return answer;
}
/*
@ -805,9 +826,13 @@ Transaction.create = function (ins, outs, opts) {
var sat = outs[i].amountSat || util.parseValue(outs[i].amount);
valueOutSat = valueOutSat.add(sat);
}
valueOutSat = valueOutSat.add(feeSat);
if (valueInSat.cmp(valueOutSat)<0) {
throw new Error('transaction inputs sum less than outputs');
var inv = valueInSat.toString();
var ouv = valueOutSat.toString();
throw new Error('transaction input amount is less than outputs: ' +
inv + ' < '+ouv + ' [SAT]');
}
var remainderSat = valueInSat.sub(valueOutSat);

View File

@ -4,13 +4,15 @@
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1",
"scriptPubKey": "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ac",
"vout": 1,
"amount": 0.01
"amount": 0.01,
"confirmations":7
},
{
"address": "mqSjTad2TKbPcKQ3Jq4kgCkKatyN44UMgZ",
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc2",
"scriptPubKey": "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ad",
"vout": 1,
"vout": 0,
"confirmations": 1,
"amount": 0.1
},
{
@ -18,6 +20,7 @@
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc3",
"scriptPubKey": "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ae",
"vout": 3,
"confirmations": 0,
"amount": 1
}
]

View File

@ -34,18 +34,18 @@ describe('Transaction', function() {
});
it('should be able to select utxos', function() {
var u = Transaction.selectUnspent(testdata.dataUnspends,1.0);
it('#_selectUnspent should be able to select utxos', function() {
var u = Transaction._selectUnspent(testdata.dataUnspends,1.0);
u.length.should.equal(3);
u = Transaction.selectUnspent(testdata.dataUnspends,0.5);
u = Transaction._selectUnspent(testdata.dataUnspends,0.5);
u.length.should.equal(3);
u = Transaction.selectUnspent(testdata.dataUnspends,0.1);
u = Transaction._selectUnspent(testdata.dataUnspends,0.1);
u.length.should.equal(2);
u = Transaction.selectUnspent(testdata.dataUnspends,0.05);
u = Transaction._selectUnspent(testdata.dataUnspends,0.05);
u.length.should.equal(2);
u = Transaction.selectUnspent(testdata.dataUnspends,0.015);
u = Transaction._selectUnspent(testdata.dataUnspends,0.015);
u.length.should.equal(2);
u = Transaction.selectUnspent(testdata.dataUnspends,0.01);
u = Transaction._selectUnspent(testdata.dataUnspends,0.01);
u.length.should.equal(1);
should.exist(u[0].amount);
should.exist(u[0].txid);
@ -53,26 +53,64 @@ describe('Transaction', function() {
should.exist(u[0].vout);
});
it('should return null if not enough utxos', function() {
it('#selectUnspent should return null if not enough utxos', function() {
var u = Transaction.selectUnspent(testdata.dataUnspends,1.12);
u.length.should.equal(0);
});
it('should be able to create instance thru #create', function() {
it('#selectUnspent should check confirmations', function() {
var u = Transaction.selectUnspent(testdata.dataUnspends,0.9);
u.length.should.equal(0);
var u = Transaction.selectUnspent(testdata.dataUnspends,0.9,true);
u.length.should.equal(3);
var u = Transaction.selectUnspent(testdata.dataUnspends,0.11);
u.length.should.equal(2);
var u = Transaction.selectUnspent(testdata.dataUnspends,0.111);
u.length.should.equal(0);
});
var opts = {remainderAddress: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'};
it('#create should be able to create instance', function() {
var utxos = Transaction.selectUnspent(testdata.dataUnspends,0.1);
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
var tx = Transaction.create(utxos, outs,
{remainderAddress:'3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou'});
var tx = Transaction.create(utxos, outs, opts);
should.exist(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);
// TODO remainder is 0.03 here because unspend just select utxos in order
util.valueToBigInt(tx.outs[1].v).cmp(3000000).should.equal(0);
// remainder is 0.0299 here because unspend select utxos in order
util.valueToBigInt(tx.outs[1].v).cmp(2990000).should.equal(0);
});
it('#create should fail if not enough inputs ', function() {
var utxos = Transaction.selectUnspent(testdata.dataUnspends,1);
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
Transaction
.create
.bind(utxos, outs, opts)
.should.throw();
});
it('#create should create same output as bitcoind createrawtransaction ', function() {
var utxos = Transaction.selectUnspent(testdata.dataUnspends,0.1);
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
var tx = Transaction.create(utxos, outs, opts);
tx.serialize().toString('hex').should.equal('0100000002c1cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0100000000ffffffffc2cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0000000000ffffffff0200127a00000000001976a914774e603bafb717bd3f070e68bbcccfd907c77d1388acb09f2d00000000001976a914b00127584485a7cff0949ef0f6bc5575f06ce00d88ac00000000');
outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
tx = Transaction.create(utxos, outs, {fee:0.03} );
tx.serialize().toString('hex').should.equal('0100000002c1cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0100000000ffffffffc2cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0000000000ffffffff0100127a00000000001976a914774e603bafb717bd3f070e68bbcccfd907c77d1388ac00000000');
});
// Read tests from test/data/tx_valid.json
// Format is an array of arrays
// Inner arrays are either [ "comment" ]