@ -1376,27 +1376,6 @@ WalletService.prototype._selectTxInputs2 = function(txp, utxosToExclude, cb) {
} ) ;
} ) ;
} ;
} ;
function formatAmount ( amount ) {
return Utils . formatAmount ( amount , 'btc' ) + 'btc' ;
} ;
function formatInputs ( inputs ) {
if ( _ . isEmpty ( inputs ) ) return 'none' ;
return _ . map ( [ ] . concat ( inputs ) , function ( i ) {
var amount = formatAmount ( i . satoshis ) ;
var confirmations = i . confirmations ? i . confirmations + 'c' : 'u' ;
return amount + '/' + confirmations ;
} ) . join ( ', ' ) ;
} ;
function formatRatio ( ratio ) {
return ( ratio * 100. ) . toFixed ( 4 ) + '%' ;
} ;
function formatSize ( size ) {
return ( size / 1000. ) . toFixed ( 4 ) + 'kB' ;
} ;
function select ( utxos , cb ) {
function select ( utxos , cb ) {
var txpAmount = txp . getTotalAmount ( ) ;
var txpAmount = txp . getTotalAmount ( ) ;
var baseTxpSize = txp . getEstimatedSize ( ) ;
var baseTxpSize = txp . getEstimatedSize ( ) ;
@ -1407,16 +1386,16 @@ WalletService.prototype._selectTxInputs2 = function(txp, utxosToExclude, cb) {
var totalValueInUtxos = _ . sum ( utxos , 'satoshis' ) ;
var totalValueInUtxos = _ . sum ( utxos , 'satoshis' ) ;
var netValueInUtxos = totalValueInUtxos - baseTxpFee - ( utxos . length * feePerInput ) ;
var netValueInUtxos = totalValueInUtxos - baseTxpFee - ( utxos . length * feePerInput ) ;
if ( totalValueInUtxos < txpAmount ) {
if ( totalValueInUtxos < txpAmount ) {
log . debug ( 'Total value in all utxos (' + formatAmount ( totalValueInUtxos ) + ') is insufficient to cover for txp amount (' + formatAmount ( txpAmount ) + ')' ) ;
log . debug ( 'Total value in all utxos (' + Utils . formatAmountInBtc ( totalValueInUtxos ) + ') is insufficient to cover for txp amount (' + Utils . formatAmountInBtc ( txpAmount ) + ')' ) ;
return cb ( Errors . INSUFFICIENT_FUNDS ) ;
return cb ( Errors . INSUFFICIENT_FUNDS ) ;
}
}
if ( netValueInUtxos < txpAmount ) {
if ( netValueInUtxos < txpAmount ) {
log . debug ( 'Value after fees in all utxos (' + formatAmount ( netValueInUtxos ) + ') is insufficient to cover for txp amount (' + formatAmount ( txpAmount ) + ')' ) ;
log . debug ( 'Value after fees in all utxos (' + Utils . formatAmountInBtc ( netValueInUtxos ) + ') is insufficient to cover for txp amount (' + Utils . formatAmountInBtc ( txpAmount ) + ')' ) ;
return cb ( Errors . INSUFFICIENT_FUNDS_FOR_FEE ) ;
return cb ( Errors . INSUFFICIENT_FUNDS_FOR_FEE ) ;
}
}
var bigInputThreshold = txpAmount * Defaults . UTXO_SELECTION_MAX_SINGLE_UTXO_FACTOR + ( baseTxpFee + feePerInput ) ;
var bigInputThreshold = txpAmount * Defaults . UTXO_SELECTION_MAX_SINGLE_UTXO_FACTOR + ( baseTxpFee + feePerInput ) ;
log . debug ( 'Big input threshold ' + formatAmount ( bigInputThreshold ) ) ;
log . debug ( 'Big input threshold ' + Utils . formatAmountInBtc ( bigInputThreshold ) ) ;
var partitions = _ . partition ( utxos , function ( utxo ) {
var partitions = _ . partition ( utxos , function ( utxo ) {
return utxo . satoshis > bigInputThreshold ;
return utxo . satoshis > bigInputThreshold ;
@ -1427,41 +1406,41 @@ WalletService.prototype._selectTxInputs2 = function(txp, utxosToExclude, cb) {
return - utxo . satoshis ;
return - utxo . satoshis ;
} ) ;
} ) ;
log . debug ( 'Considering ' + bigInputs . length + ' big inputs (' + formatInput s( bigInputs ) + ')' ) ;
log . debug ( 'Considering ' + bigInputs . length + ' big inputs (' + Utils . formatUtxo s( bigInputs ) + ')' ) ;
log . debug ( 'Considering ' + smallInputs . length + ' small inputs (' + formatInput s( smallInputs ) + ')' ) ;
log . debug ( 'Considering ' + smallInputs . length + ' small inputs (' + Utils . formatUtxo s( smallInputs ) + ')' ) ;
var total = 0 ;
var total = 0 ;
var selected = [ ] ;
var selected = [ ] ;
var error ;
var error ;
_ . each ( smallInputs , function ( input , i ) {
_ . each ( smallInputs , function ( input , i ) {
log . debug ( 'Input #' + i + ': ' + formatInput s( input ) ) ;
log . debug ( 'Input #' + i + ': ' + Utils . formatUtxo s( input ) ) ;
if ( input . satoshis < feePerInput ) {
if ( input . satoshis < feePerInput ) {
log . debug ( 'The input does not cover the extra fees (' + formatAmount ( feePerInput ) + ')' ) ;
log . debug ( 'The input does not cover the extra fees (' + Utils . formatAmountInBtc ( feePerInput ) + ')' ) ;
return false ;
return false ;
}
}
var inputAmount = input . satoshis - feePerInput ;
var inputAmount = input . satoshis - feePerInput ;
log . debug ( 'The input contributes ' + formatAmount ( inputAmount ) ) ;
log . debug ( 'The input contributes ' + Utils . formatAmountInBtc ( inputAmount ) ) ;
selected . push ( input ) ;
selected . push ( input ) ;
var txpSize = baseTxpSize + selected . length * sizePerInput ;
var txpSize = baseTxpSize + selected . length * sizePerInput ;
var txpFee = baseTxpFee + selected . length * feePerInput ;
var txpFee = baseTxpFee + selected . length * feePerInput ;
log . debug ( 'Tx size: ' + formatSize ( txpSize ) + ', Tx fee: ' + formatAmount ( txpFee ) ) ;
log . debug ( 'Tx size: ' + Utils . formatSize ( txpSize ) + ', Tx fee: ' + Utils . formatAmountInBtc ( txpFee ) ) ;
var amountVsFeeRatio = txpFee / txpAmount ;
var amountVsFeeRatio = txpFee / txpAmount ;
var singleInputFeeVsFeeRatio = txpFee / ( baseTxpFee + feePerInput ) ;
var singleInputFeeVsFeeRatio = txpFee / ( baseTxpFee + feePerInput ) ;
var amountVsUtxoRatio = inputAmount / txpAmount ;
var amountVsUtxoRatio = inputAmount / txpAmount ;
log . debug ( 'Tx amount/Fee: ' + formatRatio ( amountVsFeeRatio ) + ' (max: ' + formatRatio ( Defaults . UTXO_SELECTION_MAX_TX_AMOUNT_VS_FEE_FACTOR ) + ')' ) ;
log . debug ( 'Tx amount/Fee: ' + Utils . formatRatio ( amountVsFeeRatio ) + ' (max: ' + Utils . formatRatio ( Defaults . UTXO_SELECTION_MAX_TX_AMOUNT_VS_FEE_FACTOR ) + ')' ) ;
log . debug ( 'Single-input fee/Multi-input fee: ' + formatRatio ( singleInputFeeVsFeeRatio ) + ' (max: ' + formatRatio ( Defaults . UTXO_SELECTION_MAX_FEE_VS_SINGLE_UTXO_FEE_FACTOR ) + ')' + ' loses wrt single-input tx: ' + formatAmount ( ( selected . length - 1 ) * feePerInput ) ) ;
log . debug ( 'Single-input fee/Multi-input fee: ' + Utils . formatRatio ( singleInputFeeVsFeeRatio ) + ' (max: ' + Utils . formatRatio ( Defaults . UTXO_SELECTION_MAX_FEE_VS_SINGLE_UTXO_FEE_FACTOR ) + ')' + ' loses wrt single-input tx: ' + Utils . formatAmountInBtc ( ( selected . length - 1 ) * feePerInput ) ) ;
log . debug ( 'Tx amount/input amount:' + formatRatio ( amountVsUtxoRatio ) + ' (min: ' + formatRatio ( Defaults . UTXO_SELECTION_MIN_TX_AMOUNT_VS_UTXO_FACTOR ) + ')' ) ;
log . debug ( 'Tx amount/input amount:' + Utils . formatRatio ( amountVsUtxoRatio ) + ' (min: ' + Utils . formatRatio ( Defaults . UTXO_SELECTION_MIN_TX_AMOUNT_VS_UTXO_FACTOR ) + ')' ) ;
if ( txpSize / 1000. > Defaults . MAX_TX_SIZE_IN_KB ) {
if ( txpSize / 1000. > Defaults . MAX_TX_SIZE_IN_KB ) {
log . debug ( 'Breaking because tx size (' + formatSize ( txpSize ) + ') is too big (max: ' + formatSize ( Defaults . MAX_TX_SIZE_IN_KB * 1000. ) + ')' ) ;
log . debug ( 'Breaking because tx size (' + Utils . formatSize ( txpSize ) + ') is too big (max: ' + Utils . formatSize ( Defaults . MAX_TX_SIZE_IN_KB * 1000. ) + ')' ) ;
error = Errors . TX_MAX_SIZE_EXCEEDED ;
error = Errors . TX_MAX_SIZE_EXCEEDED ;
return false ;
return false ;
}
}
@ -1480,17 +1459,17 @@ WalletService.prototype._selectTxInputs2 = function(txp, utxosToExclude, cb) {
}
}
total += inputAmount ;
total += inputAmount ;
log . debug ( 'Cumuled total so far: ' + formatAmount ( total ) ) ;
log . debug ( 'Cumuled total so far: ' + Utils . formatAmountInBtc ( total ) ) ;
if ( total >= txpAmount ) return false ;
if ( total >= txpAmount ) return false ;
} ) ;
} ) ;
if ( total < txpAmount ) {
if ( total < txpAmount ) {
log . debug ( 'Could not reach Txp total (' + formatAmount ( txpAmount ) + '), still missing: ' + formatAmount ( txpAmount - total ) ) ;
log . debug ( 'Could not reach Txp total (' + Utils . formatAmountInBtc ( txpAmount ) + '), still missing: ' + Utils . formatAmountInBtc ( txpAmount - total ) ) ;
selected = [ ] ;
selected = [ ] ;
if ( ! _ . isEmpty ( bigInputs ) ) {
if ( ! _ . isEmpty ( bigInputs ) ) {
log . debug ( 'Using big input: ' , formatInput s( _ . first ( bigInputs ) ) ) ;
log . debug ( 'Using big input: ' , Utils . formatUtxo s( _ . first ( bigInputs ) ) ) ;
var input = _ . first ( bigInputs ) ;
var input = _ . first ( bigInputs ) ;
total = input . satoshis ;
total = input . satoshis ;
selected = [ input ] ;
selected = [ input ] ;
@ -1505,7 +1484,7 @@ WalletService.prototype._selectTxInputs2 = function(txp, utxosToExclude, cb) {
return cb ( null , selected ) ;
return cb ( null , selected ) ;
} ;
} ;
log . debug ( 'Selecting inputs for a ' + formatAmount ( txp . getTotalAmount ( ) ) + ' txp' ) ;
log . debug ( 'Selecting inputs for a ' + Utils . formatAmountInBtc ( txp . getTotalAmount ( ) ) + ' txp' ) ;
self . _ getUtxosForCurrentWallet ( null , function ( err , utxos ) {
self . _ getUtxosForCurrentWallet ( null , function ( err , utxos ) {
if ( err ) return cb ( err ) ;
if ( err ) return cb ( err ) ;
@ -1533,7 +1512,7 @@ WalletService.prototype._selectTxInputs2 = function(txp, utxosToExclude, cb) {
utxos = _ . filter ( utxos , 'confirmations' ) ;
utxos = _ . filter ( utxos , 'confirmations' ) ;
}
}
log . debug ( 'Considering ' + utxos . length + ' utxos (' + formatInput s( utxos ) + ')' ) ;
log . debug ( 'Considering ' + utxos . length + ' utxos (' + Utils . formatUtxo s( utxos ) + ')' ) ;
var groups = [ 6 , 1 ] ;
var groups = [ 6 , 1 ] ;
if ( ! txp . excludeUnconfirmedUtxos ) groups . push ( 0 ) ;
if ( ! txp . excludeUnconfirmedUtxos ) groups . push ( 0 ) ;
@ -1559,7 +1538,7 @@ WalletService.prototype._selectTxInputs2 = function(txp, utxosToExclude, cb) {
return next ( ) ;
return next ( ) ;
}
}
log . debug ( 'Candidate utxos: ' + formatInput s( candidateUtxos ) ) ;
log . debug ( 'Candidate utxos: ' + Utils . formatUtxo s( candidateUtxos ) ) ;
lastGroupLength = candidateUtxos . length ;
lastGroupLength = candidateUtxos . length ;
@ -1573,7 +1552,7 @@ WalletService.prototype._selectTxInputs2 = function(txp, utxosToExclude, cb) {
selectionError = null ;
selectionError = null ;
inputs = selected ;
inputs = selected ;
log . debug ( 'Selected inputs from this group: ' + formatInput s( inputs ) ) ;
log . debug ( 'Selected inputs from this group: ' + Utils . formatUtxo s( inputs ) ) ;
return next ( ) ;
return next ( ) ;
} ) ;
} ) ;
} , function ( err ) {
} , function ( err ) {
@ -1585,7 +1564,7 @@ WalletService.prototype._selectTxInputs2 = function(txp, utxosToExclude, cb) {
var err = self . _ checkTxAndEstimateFee ( txp ) ;
var err = self . _ checkTxAndEstimateFee ( txp ) ;
if ( ! err ) {
if ( ! err ) {
log . debug ( 'Successfully built transaction. Total fees: ' , formatAmount ( txp . fee ) ) ;
log . debug ( 'Successfully built transaction. Total fees: ' , Utils . formatAmountInBtc ( txp . fee ) ) ;
} else {
} else {
log . warn ( 'Error building transaction' , err ) ;
log . warn ( 'Error building transaction' , err ) ;
}
}
@ -1775,7 +1754,7 @@ WalletService.prototype.createTxLegacy = function(opts, cb) {
txp . version = '1.0.1' ;
txp . version = '1.0.1' ;
}
}
self . _ selectTxInputs2 ( txp , opts . utxosToExclude , function ( err ) {
self . _ selectTxInputs ( txp , opts . utxosToExclude , function ( err ) {
if ( err ) return cb ( err ) ;
if ( err ) return cb ( err ) ;
$ . checkState ( txp . inputs ) ;
$ . checkState ( txp . inputs ) ;
@ -1817,6 +1796,22 @@ WalletService.prototype.createTxLegacy = function(opts, cb) {
WalletService . prototype . createTx = function ( opts , cb ) {
WalletService . prototype . createTx = function ( opts , cb ) {
var self = this ;
var self = this ;
function logStatistics ( prefix , txp ) {
var totalAmount = txp . getTotalAmount ( ) ;
var inputs = _ . sortBy ( _ . map ( txp . inputs , function ( input ) {
return _ . pick ( input , 'satoshis' , 'confirmations' ) ;
} ) , 'satoshis' ) ;
var totalLocked = _ . sum ( inputs , 'satoshis' ) ;
var overhead = totalLocked - totalAmount ;
log . info ( prefix , 'TXP ID: ' + txp . id ) ;
log . info ( prefix , 'Total amount: ' + Utils . formatAmountInBtc ( totalAmount ) ) ;
log . info ( prefix , 'Fee: ' + Utils . formatAmountInBtc ( txp . fee ) + ' (per KB: ' + Utils . formatAmountInBtc ( txp . feePerKb ) + ')' ) ;
log . info ( prefix , 'Exclude unconfirmed: ' + txp . excludeUnconfirmedUtxos ) ;
log . info ( prefix , 'Total locked: ' + Utils . formatAmountInBtc ( totalLocked ) + ', Overhead: ' + Utils . formatAmountInBtc ( overhead ) + ' (' + Utils . formatRatio ( overhead / totalAmount ) + ')' ) ;
log . info ( prefix , 'Inputs: ' , Utils . formatUtxos ( inputs ) ) ;
} ;
if ( ! Utils . checkRequired ( opts , [ 'outputs' , 'feePerKb' ] ) )
if ( ! Utils . checkRequired ( opts , [ 'outputs' , 'feePerKb' ] ) )
return cb ( new ClientError ( 'Required argument missing' ) ) ;
return cb ( new ClientError ( 'Required argument missing' ) ) ;
@ -1859,14 +1854,27 @@ WalletService.prototype.createTx = function(opts, cb) {
var txp = Model . TxProposal . create ( txOpts ) ;
var txp = Model . TxProposal . create ( txOpts ) ;
self . _ selectTxInputs2 ( txp , opts . utxosToExclude , function ( err ) {
self . _ selectTxInputs2 ( txp , opts . utxosToExclude , function ( err ) {
if ( err ) return cb ( err ) ;
if ( err ) {
log . error ( 'Could not select inputs using new algorithm' , err ) ;
} else {
logStatistics ( 'NEW_UTXO_SEL' , txp ) ;
}
self . storage . storeAddressAndWallet ( wallet , txp . changeAddress , function ( err ) {
txp . setInputs ( [ ] ) ;
txp . fee = null ;
self . _ selectTxInputs ( txp , opts . utxosToExclude , function ( err ) {
if ( err ) return cb ( err ) ;
if ( err ) return cb ( err ) ;
self . storage . storeTx ( wallet . id , txp , function ( err ) {
logStatistics ( 'OLD_UTXO_SEL' , txp ) ;
self . storage . storeAddressAndWallet ( wallet , txp . changeAddress , function ( err ) {
if ( err ) return cb ( err ) ;
if ( err ) return cb ( err ) ;
return cb ( null , txp ) ;
self . storage . storeTx ( wallet . id , txp , function ( err ) {
if ( err ) return cb ( err ) ;
return cb ( null , txp ) ;
} ) ;
} ) ;
} ) ;
} ) ;
} ) ;
} ) ;
} ) ;