#create for Transaction and tests
This commit is contained in:
parent
671d372c19
commit
706162e2ba
@ -652,6 +652,47 @@ Transaction.prototype.parse = function (parser) {
|
|||||||
this.calcHash();
|
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
|
* selectUnspent
|
||||||
*
|
*
|
||||||
@ -664,51 +705,31 @@ Transaction.prototype.parse = function (parser) {
|
|||||||
* scriptPubKey: "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ac",
|
* scriptPubKey: "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ac",
|
||||||
* vout: 1,
|
* vout: 1,
|
||||||
* amount: 0.01,
|
* amount: 0.01,
|
||||||
|
* confirmations: 3
|
||||||
* }, [...]
|
* }, [...]
|
||||||
* ]
|
* ]
|
||||||
* This is compatible con insight's /utxo API.
|
* 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.
|
* amountSat can be given to provide amount in satochis.
|
||||||
*
|
*
|
||||||
* @totalNeededAmount: output transaction amount in BTC, including fee
|
* @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.
|
* Note that the sum of the selected unspent is >= the desired amount.
|
||||||
* It does not check for confirmations. The unspendArray should be filtered.
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
Transaction.selectUnspent = function (unspentArray, totalNeededAmount) {
|
|
||||||
|
|
||||||
// TODO implement bidcoind heristics
|
Transaction.selectUnspent = function (unspentArray, totalNeededAmount, allowUnconfirmed) {
|
||||||
// A-
|
var answer = Transaction._selectUnspent(unspentArray, totalNeededAmount, 6);
|
||||||
// 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;
|
|
||||||
|
|
||||||
for(var i = 0; i<l; i++) {
|
if (!answer.length)
|
||||||
var u = unspentArray[i];
|
answer = Transaction._selectUnspent(unspentArray, totalNeededAmount, 1);
|
||||||
var sat = u.amountSat || util.parseValue(u.amount);
|
|
||||||
totalSat = totalSat.add(sat);
|
if (!answer.length && allowUnconfirmed)
|
||||||
selected.push(u);
|
answer = Transaction._selectUnspent(unspentArray, totalNeededAmount, 0);
|
||||||
if(totalSat.cmp(totalNeededAmountSat) >= 0) {
|
|
||||||
fullfill = true;
|
return answer;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!fullfill) return [];
|
|
||||||
return selected;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -805,9 +826,13 @@ Transaction.create = function (ins, outs, opts) {
|
|||||||
var sat = outs[i].amountSat || util.parseValue(outs[i].amount);
|
var sat = outs[i].amountSat || util.parseValue(outs[i].amount);
|
||||||
valueOutSat = valueOutSat.add(sat);
|
valueOutSat = valueOutSat.add(sat);
|
||||||
}
|
}
|
||||||
|
valueOutSat = valueOutSat.add(feeSat);
|
||||||
|
|
||||||
if (valueInSat.cmp(valueOutSat)<0) {
|
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);
|
var remainderSat = valueInSat.sub(valueOutSat);
|
||||||
|
|||||||
@ -4,13 +4,15 @@
|
|||||||
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1",
|
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1",
|
||||||
"scriptPubKey": "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ac",
|
"scriptPubKey": "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ac",
|
||||||
"vout": 1,
|
"vout": 1,
|
||||||
"amount": 0.01
|
"amount": 0.01,
|
||||||
|
"confirmations":7
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"address": "mqSjTad2TKbPcKQ3Jq4kgCkKatyN44UMgZ",
|
"address": "mqSjTad2TKbPcKQ3Jq4kgCkKatyN44UMgZ",
|
||||||
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc2",
|
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc2",
|
||||||
"scriptPubKey": "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ad",
|
"scriptPubKey": "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ad",
|
||||||
"vout": 1,
|
"vout": 0,
|
||||||
|
"confirmations": 1,
|
||||||
"amount": 0.1
|
"amount": 0.1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -18,6 +20,7 @@
|
|||||||
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc3",
|
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc3",
|
||||||
"scriptPubKey": "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ae",
|
"scriptPubKey": "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ae",
|
||||||
"vout": 3,
|
"vout": 3,
|
||||||
|
"confirmations": 0,
|
||||||
"amount": 1
|
"amount": 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -34,18 +34,18 @@ describe('Transaction', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should be able to select utxos', function() {
|
it('#_selectUnspent should be able to select utxos', function() {
|
||||||
var u = Transaction.selectUnspent(testdata.dataUnspends,1.0);
|
var u = Transaction._selectUnspent(testdata.dataUnspends,1.0);
|
||||||
u.length.should.equal(3);
|
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.length.should.equal(3);
|
||||||
u = Transaction.selectUnspent(testdata.dataUnspends,0.1);
|
u = Transaction._selectUnspent(testdata.dataUnspends,0.1);
|
||||||
u.length.should.equal(2);
|
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.length.should.equal(2);
|
||||||
u = Transaction.selectUnspent(testdata.dataUnspends,0.015);
|
u = Transaction._selectUnspent(testdata.dataUnspends,0.015);
|
||||||
u.length.should.equal(2);
|
u.length.should.equal(2);
|
||||||
u = Transaction.selectUnspent(testdata.dataUnspends,0.01);
|
u = Transaction._selectUnspent(testdata.dataUnspends,0.01);
|
||||||
u.length.should.equal(1);
|
u.length.should.equal(1);
|
||||||
should.exist(u[0].amount);
|
should.exist(u[0].amount);
|
||||||
should.exist(u[0].txid);
|
should.exist(u[0].txid);
|
||||||
@ -53,26 +53,64 @@ describe('Transaction', function() {
|
|||||||
should.exist(u[0].vout);
|
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);
|
var u = Transaction.selectUnspent(testdata.dataUnspends,1.12);
|
||||||
u.length.should.equal(0);
|
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 utxos = Transaction.selectUnspent(testdata.dataUnspends,0.1);
|
||||||
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
||||||
var tx = Transaction.create(utxos, outs,
|
var tx = Transaction.create(utxos, outs, opts);
|
||||||
{remainderAddress:'3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou'});
|
|
||||||
should.exist(tx);
|
should.exist(tx);
|
||||||
|
|
||||||
tx.version.should.equal(1);
|
tx.version.should.equal(1);
|
||||||
tx.ins.length.should.equal(2);
|
tx.ins.length.should.equal(2);
|
||||||
tx.outs.length.should.equal(2);
|
tx.outs.length.should.equal(2);
|
||||||
util.valueToBigInt(tx.outs[0].v).cmp(8000000).should.equal(0);
|
util.valueToBigInt(tx.outs[0].v).cmp(8000000).should.equal(0);
|
||||||
// TODO remainder is 0.03 here because unspend just select utxos in order
|
// remainder is 0.0299 here because unspend select utxos in order
|
||||||
util.valueToBigInt(tx.outs[1].v).cmp(3000000).should.equal(0);
|
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
|
// Read tests from test/data/tx_valid.json
|
||||||
// Format is an array of arrays
|
// Format is an array of arrays
|
||||||
// Inner arrays are either [ "comment" ]
|
// Inner arrays are either [ "comment" ]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user