diff --git a/Transaction.js b/Transaction.js index 1c55c7827..3393f3698 100644 --- a/Transaction.js +++ b/Transaction.js @@ -683,51 +683,12 @@ 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= 0) { - fullfill = true; - break; - } - } - if (!fullfill) return []; - return selected; -} /* * selectUnspent * - * Selects some unspend outputs for later usage in tx inputs + * Selects some unspent outputs for later usage in tx inputs * * @unspentArray: unspent array (UTXO) avaible on the form: * [{ @@ -739,28 +700,56 @@ Transaction._selectUnspent = function (unspentArray, totalNeededAmount, minConfi * confirmations: 3 * }, [...] * ] - * This is compatible con insight's /utxo API. - * That amount is in BTCs. (as returned in insight and bitcoind) - * amountSat can be given to provide amount in satochis. + * This is compatible con insight's utxo API. + * That amount is in BTCs (as returned in insight and bitcoind). + * amountSat (instead of amount) can be given to provide amount in satochis. * * @totalNeededAmount: output transaction amount in BTC, including fee - * @allowUnconfirmed:false (allow selecting unconfirmed utxos) - * + * @allowUnconfirmed: false (allow selecting unconfirmed utxos) * * Note that the sum of the selected unspent is >= the desired amount. + * Returns the selected unspent outputs if the totalNeededAmount was reach. + * 'null' if not. + * + * TODO: utxo selection is not optimized to minimize mempool usage. * */ Transaction.selectUnspent = function (unspentArray, totalNeededAmount, allowUnconfirmed) { - var answer = Transaction._selectUnspent(unspentArray, totalNeededAmount, 6); - if (!answer.length) - answer = Transaction._selectUnspent(unspentArray, totalNeededAmount, 1); + var minConfirmationSteps = [6,1]; + if (allowUnconfirmed) minConfirmationSteps.push(0); - if (!answer.length && allowUnconfirmed) - answer = Transaction._selectUnspent(unspentArray, totalNeededAmount, 0); + var ret = []; + var l = unspentArray.length; + var totalSat = bignum(0); + var totalNeededAmountSat = util.parseValue(totalNeededAmount); + var fulfill = false; + var maxConfirmations = null; - return answer; + do { + var minConfirmations = minConfirmationSteps.shift(); + for(var i = 0; i=maxConfirmations) ) + continue; + + + var sat = u.amountSat || util.parseValue(u.amount); + totalSat = totalSat.add(sat); + ret.push(u); + if(totalSat.cmp(totalNeededAmountSat) >= 0) { + fulfill = true; + break; + } + } + maxConfirmations = minConfirmations; + } while( !fulfill && minConfirmationSteps.length); + + return fulfill ? ret : null; } /* diff --git a/test/test.Transaction.js b/test/test.Transaction.js index 875559266..3631f04ac 100644 --- a/test/test.Transaction.js +++ b/test/test.Transaction.js @@ -34,41 +34,47 @@ describe('Transaction', function() { }); - it('#_selectUnspent should be able to select utxos', function() { - var u = Transaction._selectUnspent(testdata.dataUnspent,1.0); + it('#selectUnspent should be able to select utxos', function() { + var u = Transaction.selectUnspent(testdata.dataUnspent,1.0, true); u.length.should.equal(3); - u = Transaction._selectUnspent(testdata.dataUnspent,0.5); - u.length.should.equal(3); - u = Transaction._selectUnspent(testdata.dataUnspent,0.1); - u.length.should.equal(2); - u = Transaction._selectUnspent(testdata.dataUnspent,0.05); - u.length.should.equal(2); - u = Transaction._selectUnspent(testdata.dataUnspent,0.015); - u.length.should.equal(2); - u = Transaction._selectUnspent(testdata.dataUnspent,0.01); - u.length.should.equal(1); + 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); - u.length.should.equal(0); + should.not.exist(u); }); it('#selectUnspent should check confirmations', function() { var u = Transaction.selectUnspent(testdata.dataUnspent,0.9); - u.length.should.equal(0); + 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); - u.length.should.equal(0); + should.not.exist(u); });