@ -239,6 +239,8 @@ describe('Wallet service', function() {
} ) ;
describe ( '#joinWallet' , function ( ) {
describe ( 'New clients' , function ( ) {
var server , walletId ;
beforeEach ( function ( done ) {
server = new WalletService ( ) ;
@ -526,7 +528,7 @@ describe('Wallet service', function() {
} ) ;
} ) ;
describe ( '#joinWallet new/legacy clients' , function ( ) {
describe ( 'Interaction new/legacy clients' , function ( ) {
var server ;
beforeEach ( function ( ) {
server = new WalletService ( ) ;
@ -556,6 +558,7 @@ describe('Wallet service', function() {
} ) ;
} ) ;
} ) ;
it ( 'should fail to join new wallet from legacy client' , function ( done ) {
var walletOpts = {
name : 'my wallet' ,
@ -581,6 +584,7 @@ describe('Wallet service', function() {
} ) ;
} ) ;
} ) ;
} ) ;
describe ( 'Address derivation strategy' , function ( ) {
var server ;
@ -2128,6 +2132,7 @@ describe('Wallet service', function() {
} ) ;
it ( 'should use unconfirmed utxos only when no more confirmed utxos are available' , function ( done ) {
// log.level = 'debug';
helpers . stubUtxos ( server , wallet , [ 1.3 , 'u0.5' , 'u0.1' , 1.2 ] , function ( utxos ) {
var txOpts = helpers . createSimpleProposalOpts ( '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7' , 2.55 , TestData . copayers [ 0 ] . privKey_1H_0 , {
message : 'some message'
@ -2350,10 +2355,11 @@ describe('Wallet service', function() {
} ) ;
it ( 'should fail to create a tx exceeding max size in kb' , function ( done ) {
helpers . stubUtxos ( server , wallet , _ . range ( 1 , 10 , 0 ) , function ( ) {
var txOpts = helpers . createSimpleProposalOpts ( '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7' , 9 , TestData . copayers [ 0 ] . privKey_1H_0 ) ;
// log.level = 'debug';
var _ oldDefault = Defaults . MAX_TX_SIZE_IN_KB ;
Defaults . MAX_TX_SIZE_IN_KB = 1 ;
helpers . stubUtxos ( server , wallet , _ . range ( 1 , 10 , 0 ) , function ( ) {
var txOpts = helpers . createSimpleProposalOpts ( '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7' , 8 , TestData . copayers [ 0 ] . privKey_1H_0 ) ;
server . createTxLegacy ( txOpts , function ( err , tx ) {
should . exist ( err ) ;
err . code . should . equal ( 'TX_MAX_SIZE_EXCEEDED' ) ;
@ -2375,19 +2381,18 @@ describe('Wallet service', function() {
} ) ;
} ) ;
it ( 'should fail to create tx that would return change for dust amount' , function ( done ) {
it ( 'should modify fee if tx would return change for dust amount' , function ( done ) {
helpers . stubUtxos ( server , wallet , [ 1 ] , function ( ) {
var fee = 4095 / 1e8 ; // The exact fee of the resulting tx
var change = 100 / 1e8 ; // Below dust
var amount = 1 - fee - change ;
var fee = 4095 ; // The exact fee of the resulting tx (based exclusively on feePerKB && size)
var change = 100 ; // Below dust
var amount = ( 1e8 - fee - change ) / 1e8 ;
var txOpts = helpers . createSimpleProposalOpts ( '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7' , amount , TestData . copayers [ 0 ] . privKey_1H_0 , {
feePerKb : 10000
} ) ;
server . createTxLegacy ( txOpts , function ( err , tx ) {
should . exist ( err ) ;
err . code . should . equal ( 'DUST_AMOUNT' ) ;
err . message . should . equal ( 'Amount below dust threshold' ) ;
should . not . exist ( err ) ;
tx . fee . should . equal ( fee + change ) ;
done ( ) ;
} ) ;
} ) ;
@ -3085,10 +3090,32 @@ describe('Wallet service', function() {
} ) ;
} ) ;
it ( 'should be able to specify inputs & absolute fee' , function ( done ) {
helpers . stubUtxos ( server , wallet , [ 1 , 2 ] , function ( utxos ) {
var txOpts = {
outputs : [ {
toAddress : '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7' ,
amount : 0.8 e8 ,
} ] ,
inputs : utxos ,
fee : 1000e2 ,
} ;
server . createTx ( txOpts , function ( err , tx ) {
should . not . exist ( err ) ;
should . exist ( tx ) ;
tx . amount . should . equal ( helpers . toSatoshi ( 0.8 ) ) ;
should . not . exist ( tx . feePerKb ) ;
tx . fee . should . equal ( 1000e2 ) ;
var t = tx . getBitcoreTx ( ) ;
t . getFee ( ) . should . equal ( 1000e2 ) ;
t . getChangeOutput ( ) . satoshis . should . equal ( 3e8 - 0.8 e8 - 1000e2 ) ;
done ( ) ;
} ) ;
} ) ;
} ) ;
} ) ;
describe ( '#createTx backoff time' , function ( done ) {
describe ( 'B ackoff time' , function ( done ) {
var server , wallet , txid ;
beforeEach ( function ( done ) {
@ -3165,6 +3192,397 @@ describe('Wallet service', function() {
} ) ;
} ) ;
describe ( 'UTXO Selection' , function ( ) {
var server , wallet ;
beforeEach ( function ( done ) {
// log.level = 'debug';
helpers . createAndJoinWallet ( 2 , 3 , function ( s , w ) {
server = s ;
wallet = w ;
done ( ) ;
} ) ;
} ) ;
afterEach ( function ( ) {
log . level = 'info' ;
} ) ;
it ( 'should select a single utxo if within thresholds relative to tx amount' , function ( done ) {
helpers . stubUtxos ( server , wallet , [ 1 , '350bit' , '100bit' , '100bit' , '100bit' ] , function ( ) {
var txOpts = {
outputs : [ {
toAddress : '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7' ,
amount : 200e2 ,
} ] ,
feePerKb : 10e2 ,
} ;
server . createTx ( txOpts , function ( err , txp ) {
should . not . exist ( err ) ;
should . exist ( txp ) ;
txp . inputs . length . should . equal ( 1 ) ;
txp . inputs [ 0 ] . satoshis . should . equal ( 35000 ) ;
done ( ) ;
} ) ;
} ) ;
} ) ;
it ( 'should return inputs in random order' , function ( done ) {
// NOTE: this test has a chance of failing of 1 in 1'073'741'824 :P
helpers . stubUtxos ( server , wallet , _ . range ( 1 , 31 ) , function ( utxos ) {
var txOpts = {
outputs : [ {
toAddress : '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7' ,
amount : _ . sum ( utxos , 'satoshis' ) - 0.5 e8 ,
} ] ,
feePerKb : 100e2 ,
} ;
server . createTx ( txOpts , function ( err , txp ) {
should . not . exist ( err ) ;
should . exist ( txp ) ;
var amounts = _ . pluck ( txp . inputs , 'satoshis' ) ;
amounts . length . should . equal ( 30 ) ;
_ . all ( amounts , function ( amount , i ) {
if ( i == 0 ) return true ;
return amount < amounts [ i - 1 ] ;
} ) . should . be . false ;
done ( ) ;
} ) ;
} ) ;
} ) ;
it ( 'should select a confirmed utxos if within thresholds relative to tx amount' , function ( done ) {
helpers . stubUtxos ( server , wallet , [ 1 , 'u 350bit' , '100bit' , '100bit' , '100bit' ] , function ( ) {
var txOpts = {
outputs : [ {
toAddress : '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7' ,
amount : 200e2 ,
} ] ,
feePerKb : 10e2 ,
} ;
server . createTx ( txOpts , function ( err , txp ) {
should . not . exist ( err ) ;
should . exist ( txp ) ;
txp . inputs . length . should . equal ( 3 ) ;
txp . inputs [ 0 ] . satoshis . should . equal ( 10000 ) ;
done ( ) ;
} ) ;
} ) ;
} ) ;
it ( 'should select smaller utxos if within fee constraints' , function ( done ) {
helpers . stubUtxos ( server , wallet , [ 1 , '800bit' , '800bit' , '800bit' ] , function ( ) {
var txOpts = {
outputs : [ {
toAddress : '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7' ,
amount : 2000e2 ,
} ] ,
feePerKb : 10e2 ,
} ;
server . createTx ( txOpts , function ( err , txp ) {
should . not . exist ( err ) ;
should . exist ( txp ) ;
txp . inputs . length . should . equal ( 3 ) ;
_ . all ( txp . inputs , function ( input ) {
return input == 100e2 ;
} ) ;
done ( ) ;
} ) ;
} ) ;
} ) ;
it ( 'should select smallest big utxo if small utxos are insufficient' , function ( done ) {
helpers . stubUtxos ( server , wallet , [ 3 , 1 , 2 , '100bit' , '100bit' , '100bit' ] , function ( ) {
var txOpts = {
outputs : [ {
toAddress : '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7' ,
amount : 300e2 ,
} ] ,
feePerKb : 10e2 ,
} ;
server . createTx ( txOpts , function ( err , txp ) {
should . not . exist ( err ) ;
should . exist ( txp ) ;
txp . inputs . length . should . equal ( 1 ) ;
txp . inputs [ 0 ] . satoshis . should . equal ( 1e8 ) ;
done ( ) ;
} ) ;
} ) ;
} ) ;
it ( 'should account for fee when selecting smallest big utxo' , function ( done ) {
// log.level = 'debug';
var _ old = Defaults . UTXO_SELECTION_MAX_SINGLE_UTXO_FACTOR ;
Defaults . UTXO_SELECTION_MAX_SINGLE_UTXO_FACTOR = 2 ;
// The 605 bits input cannot be selected even if it is > 2 * tx amount
// because it cannot cover for fee on its own.
helpers . stubUtxos ( server , wallet , [ 1 , '605bit' , '100bit' , '100bit' , '100bit' ] , function ( ) {
var txOpts = {
outputs : [ {
toAddress : '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7' ,
amount : 300e2 ,
} ] ,
feePerKb : 1200e2 ,
} ;
server . createTx ( txOpts , function ( err , txp ) {
should . not . exist ( err ) ;
should . exist ( txp ) ;
txp . inputs . length . should . equal ( 1 ) ;
txp . inputs [ 0 ] . satoshis . should . equal ( 1e8 ) ;
Defaults . UTXO_SELECTION_MAX_SINGLE_UTXO_FACTOR = _ old ;
done ( ) ;
} ) ;
} ) ;
} ) ;
it ( 'should select smallest big utxo if small utxos exceed maximum fee' , function ( done ) {
helpers . stubUtxos ( server , wallet , [ 3 , 1 , 2 ] . concat ( _ . times ( 20 , function ( ) {
return '1000bit' ;
} ) ) , function ( ) {
var txOpts = {
outputs : [ {
toAddress : '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7' ,
amount : 12000e2 ,
} ] ,
feePerKb : 20e2 ,
} ;
server . createTx ( txOpts , function ( err , txp ) {
should . not . exist ( err ) ;
should . exist ( txp ) ;
txp . inputs . length . should . equal ( 1 ) ;
txp . inputs [ 0 ] . satoshis . should . equal ( 1e8 ) ;
done ( ) ;
} ) ;
} ) ;
} ) ;
it ( 'should select smallest big utxo if small utxos are below accepted ratio of txp amount' , function ( done ) {
helpers . stubUtxos ( server , wallet , [ 9 , 1 , 1 , 0.5 , 0.2 , 0.2 , 0.2 ] , function ( ) {
var txOpts = {
outputs : [ {
toAddress : '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7' ,
amount : 3e8 ,
} ] ,
feePerKb : 10e2 ,
} ;
server . createTx ( txOpts , function ( err , txp ) {
should . not . exist ( err ) ;
should . exist ( txp ) ;
txp . inputs . length . should . equal ( 1 ) ;
txp . inputs [ 0 ] . satoshis . should . equal ( 9e8 ) ;
done ( ) ;
} ) ;
} ) ;
} ) ;
it ( 'should not fail with tx exceeded max size if there is at least 1 big input' , function ( done ) {
var _ old1 = Defaults . UTXO_SELECTION_MIN_TX_AMOUNT_VS_UTXO_FACTOR ;
var _ old2 = Defaults . MAX_TX_SIZE_IN_KB ;
Defaults . UTXO_SELECTION_MIN_TX_AMOUNT_VS_UTXO_FACTOR = 0.0001 ;
Defaults . MAX_TX_SIZE_IN_KB = 3 ;
helpers . stubUtxos ( server , wallet , [ 100 ] . concat ( _ . range ( 1 , 20 , 0 ) ) , function ( ) {
var txOpts = {
outputs : [ {
toAddress : '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7' ,
amount : 15e8 ,
} ] ,
feePerKb : 120e2 ,
} ;
server . createTx ( txOpts , function ( err , txp ) {
should . not . exist ( err ) ;
should . exist ( txp ) ;
txp . inputs . length . should . equal ( 1 ) ;
txp . inputs [ 0 ] . satoshis . should . equal ( 100e8 ) ;
Defaults . UTXO_SELECTION_MIN_TX_AMOUNT_VS_UTXO_FACTOR = _ old1 ;
Defaults . MAX_TX_SIZE_IN_KB = _ old2 ;
done ( ) ;
} ) ;
} ) ;
} ) ;
it ( 'should ignore utxos not contributing enough to cover increase in fee' , function ( done ) {
helpers . stubUtxos ( server , wallet , [ '100bit' , '100bit' , '100bit' ] , function ( ) {
var txOpts = {
outputs : [ {
toAddress : '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7' ,
amount : 200e2 ,
} ] ,
feePerKb : 80e2 ,
} ;
server . createTx ( txOpts , function ( err , txp ) {
should . not . exist ( err ) ;
should . exist ( txp ) ;
txp . inputs . length . should . equal ( 3 ) ;
txOpts . feePerKb = 120e2 ;
server . createTx ( txOpts , function ( err , txp ) {
should . exist ( err ) ;
should . not . exist ( txp ) ;
done ( ) ;
} ) ;
} ) ;
} ) ;
} ) ;
it ( 'should fail to select utxos if not enough to cover tx amount' , function ( done ) {
helpers . stubUtxos ( server , wallet , [ '100bit' , '100bit' , '100bit' ] , function ( ) {
var txOpts = {
outputs : [ {
toAddress : '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7' ,
amount : 400e2 ,
} ] ,
feePerKb : 10e2 ,
} ;
server . createTx ( txOpts , function ( err , txp ) {
should . exist ( err ) ;
should . not . exist ( txp ) ;
err . code . should . equal ( 'INSUFFICIENT_FUNDS' ) ;
done ( ) ;
} ) ;
} ) ;
} ) ;
it ( 'should fail to select utxos if not enough to cover fees' , function ( done ) {
helpers . stubUtxos ( server , wallet , [ '100bit' , '100bit' , '100bit' ] , function ( ) {
var txOpts = {
outputs : [ {
toAddress : '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7' ,
amount : 299e2 ,
} ] ,
feePerKb : 10e2 ,
} ;
server . createTx ( txOpts , function ( err , txp ) {
should . exist ( err ) ;
should . not . exist ( txp ) ;
err . code . should . equal ( 'INSUFFICIENT_FUNDS_FOR_FEE' ) ;
done ( ) ;
} ) ;
} ) ;
} ) ;
it ( 'should prefer a higher fee (breaking all limits) if inputs have 6+ confirmations' , function ( done ) {
helpers . stubUtxos ( server , wallet , [ '2c 2000bit' ] . concat ( _ . times ( 20 , function ( ) {
return '100bit' ;
} ) ) , function ( ) {
var txOpts = {
outputs : [ {
toAddress : '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7' ,
amount : 1500e2 ,
} ] ,
feePerKb : 10e2 ,
} ;
server . createTx ( txOpts , function ( err , txp ) {
should . not . exist ( err ) ;
should . exist ( txp ) ;
_ . all ( txp . inputs , function ( input ) {
return input == 100e2 ;
} ) ;
done ( ) ;
} ) ;
} ) ;
} ) ;
it ( 'should select unconfirmed utxos if not enough confirmed utxos' , function ( done ) {
// log.level = 'debug';
helpers . stubUtxos ( server , wallet , [ 'u 1btc' , '0.5btc' ] , function ( ) {
var txOpts = {
outputs : [ {
toAddress : '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7' ,
amount : 0.8 e8 ,
} ] ,
feePerKb : 100e2 ,
} ;
server . createTx ( txOpts , function ( err , txp ) {
should . not . exist ( err ) ;
should . exist ( txp ) ;
txp . inputs . length . should . equal ( 1 ) ;
txp . inputs [ 0 ] . satoshis . should . equal ( 1e8 ) ;
done ( ) ;
} ) ;
} ) ;
} ) ;
it ( 'should ignore utxos too small to pay for fee' , function ( done ) {
helpers . stubUtxos ( server , wallet , [ '1c200bit' , '200bit' ] . concat ( _ . times ( 20 , function ( ) {
return '1bit' ;
} ) ) , function ( ) {
var txOpts = {
outputs : [ {
toAddress : '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7' ,
amount : 200e2 ,
} ] ,
feePerKb : 90e2 ,
} ;
server . createTx ( txOpts , function ( err , txp ) {
should . not . exist ( err ) ;
should . exist ( txp ) ;
txp . inputs . length . should . equal ( 2 ) ;
done ( ) ;
} ) ;
} ) ;
} ) ;
it ( 'should use small utxos if fee is low' , function ( done ) {
helpers . stubUtxos ( server , wallet , [ ] . concat ( _ . times ( 10 , function ( ) {
return '30bit' ;
} ) ) , function ( ) {
var txOpts = {
outputs : [ {
toAddress : '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7' ,
amount : 200e2 ,
} ] ,
feePerKb : 10e2 ,
} ;
server . createTx ( txOpts , function ( err , txp ) {
should . not . exist ( err ) ;
should . exist ( txp ) ;
txp . inputs . length . should . equal ( 8 ) ;
done ( ) ;
} ) ;
} ) ;
} ) ;
it ( 'should correct fee if resulting change would be below dust' , function ( done ) {
helpers . stubUtxos ( server , wallet , [ '200bit' , '500sat' ] , function ( ) {
var txOpts = {
outputs : [ {
toAddress : '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7' ,
amount : 200e2 ,
} ] ,
feePerKb : 400 ,
} ;
server . createTx ( txOpts , function ( err , txp ) {
should . not . exist ( err ) ;
( _ . sum ( txp . inputs , 'satoshis' ) - txp . outputs [ 0 ] . amount - txp . fee ) . should . equal ( 0 ) ;
var changeOutput = txp . getBitcoreTx ( ) . getChangeOutput ( ) ;
should . not . exist ( changeOutput ) ;
done ( ) ;
} ) ;
} ) ;
} ) ;
it ( 'should ignore small utxos if fee is higher' , function ( done ) {
helpers . stubUtxos ( server , wallet , [ ] . concat ( _ . times ( 10 , function ( ) {
return '30bit' ;
} ) ) , function ( ) {
var txOpts = {
outputs : [ {
toAddress : '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7' ,
amount : 200e2 ,
} ] ,
feePerKb : 50e2 ,
} ;
server . createTx ( txOpts , function ( err , txp ) {
should . exist ( err ) ;
err . code . should . equal ( 'INSUFFICIENT_FUNDS_FOR_FEE' ) ;
done ( ) ;
} ) ;
} ) ;
} ) ;
it ( 'should always select inputs as long as there are sufficient funds' , function ( done ) {
helpers . stubUtxos ( server , wallet , [ 80 , '50bit' , '50bit' , '50bit' , '50bit' , '50bit' ] , function ( ) {
var txOpts = {
outputs : [ {
toAddress : '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7' ,
amount : 101e2 ,
} ] ,
feePerKb : 100e2 ,
} ;
server . createTx ( txOpts , function ( err , txp ) {
should . not . exist ( err ) ;
should . exist ( txp ) ;
done ( ) ;
} ) ;
} ) ;
} ) ;
} ) ;
} ) ;
describe ( '#rejectTx' , function ( ) {
var server , wallet , txid ;
@ -5023,7 +5441,6 @@ describe('Wallet service', function() {
done ( ) ;
} ) ;
} ) ;
} ) ;
describe ( '#scan' , function ( ) {
@ -5686,5 +6103,4 @@ describe('Wallet service', function() {
} ) ;
} ) ;
} ) ;
} ) ;