decred.org/dcrdex@v1.0.3/client/asset/btc/coin_selection_test.go (about)

     1  package btc
     2  
     3  import (
     4  	"math/rand"
     5  	"reflect"
     6  	"sort"
     7  	"testing"
     8  	"time"
     9  
    10  	dexbtc "decred.org/dcrdex/dex/networks/btc"
    11  )
    12  
    13  func Test_leastOverFund(t *testing.T) {
    14  	enough := func(_, _, sum uint64) (bool, uint64) {
    15  		return sum >= 10e8, 0
    16  	}
    17  	newU := func(amt float64) *CompositeUTXO {
    18  		return &CompositeUTXO{
    19  			UTxO: &UTxO{
    20  				Amount: uint64(amt) * 1e8,
    21  			},
    22  			Input: &dexbtc.SpendInfo{},
    23  		}
    24  	}
    25  	tests := []struct {
    26  		name  string
    27  		utxos []*CompositeUTXO
    28  		want  []*CompositeUTXO
    29  	}{
    30  		{
    31  			"1,3",
    32  			[]*CompositeUTXO{newU(1), newU(8), newU(9)},
    33  			[]*CompositeUTXO{newU(1), newU(9)},
    34  		},
    35  		{
    36  			"1,2",
    37  			[]*CompositeUTXO{newU(1), newU(9)},
    38  			[]*CompositeUTXO{newU(1), newU(9)},
    39  		},
    40  		{
    41  			"1,2++",
    42  			[]*CompositeUTXO{newU(2), newU(9)},
    43  			[]*CompositeUTXO{newU(2), newU(9)},
    44  		},
    45  		{
    46  			"2,3++",
    47  			[]*CompositeUTXO{newU(0), newU(2), newU(9)},
    48  			[]*CompositeUTXO{newU(2), newU(9)},
    49  		},
    50  		{
    51  			"3",
    52  			[]*CompositeUTXO{newU(0), newU(2), newU(10)},
    53  			[]*CompositeUTXO{newU(10)},
    54  		},
    55  		{
    56  			"subset",
    57  			[]*CompositeUTXO{newU(1), newU(9), newU(11)},
    58  			[]*CompositeUTXO{newU(1), newU(9)},
    59  		},
    60  		{
    61  			"subset small bias",
    62  			[]*CompositeUTXO{newU(3), newU(6), newU(7)},
    63  			[]*CompositeUTXO{newU(3), newU(7)},
    64  		},
    65  		{
    66  			"single exception",
    67  			[]*CompositeUTXO{newU(5), newU(7), newU(11)},
    68  			[]*CompositeUTXO{newU(11)},
    69  		},
    70  		{
    71  			"1 of 1",
    72  			[]*CompositeUTXO{newU(10)},
    73  			[]*CompositeUTXO{newU(10)},
    74  		},
    75  		{
    76  			"ok nil",
    77  			[]*CompositeUTXO{newU(1), newU(8)},
    78  			nil,
    79  		},
    80  		{
    81  			"ok",
    82  			[]*CompositeUTXO{newU(1)},
    83  			nil,
    84  		},
    85  	}
    86  	for _, tt := range tests {
    87  		t.Run(tt.name, func(t *testing.T) {
    88  			got := leastOverFund(enough, tt.utxos)
    89  			sort.Slice(got, func(i int, j int) bool {
    90  				return got[i].Amount < got[j].Amount
    91  			})
    92  			if !reflect.DeepEqual(got, tt.want) {
    93  				t.Errorf("subset() = %v, want %v", got, tt.want)
    94  			}
    95  		})
    96  	}
    97  }
    98  
    99  func Test_leastOverFundWithLimit(t *testing.T) {
   100  	enough := func(_, _, sum uint64) (bool, uint64) {
   101  		return sum >= 10e8, 0
   102  	}
   103  	newU := func(amt float64) *CompositeUTXO {
   104  		return &CompositeUTXO{
   105  			UTxO: &UTxO{
   106  				Amount: uint64(amt) * 1e8,
   107  			},
   108  			Input: &dexbtc.SpendInfo{},
   109  		}
   110  	}
   111  	tests := []struct {
   112  		name  string
   113  		limit uint64
   114  		utxos []*CompositeUTXO
   115  		want  []*CompositeUTXO
   116  	}{
   117  		{
   118  			"1,3",
   119  			10e8,
   120  			[]*CompositeUTXO{newU(1), newU(8), newU(9)},
   121  			[]*CompositeUTXO{newU(1), newU(9)},
   122  		},
   123  		{
   124  			"max fund too low",
   125  			9e8,
   126  			[]*CompositeUTXO{newU(1), newU(8), newU(9)},
   127  			nil,
   128  		},
   129  	}
   130  	for _, tt := range tests {
   131  		t.Run(tt.name, func(t *testing.T) {
   132  			got := leastOverFundWithLimit(enough, tt.limit, tt.utxos)
   133  			sort.Slice(got, func(i int, j int) bool {
   134  				return got[i].Amount < got[j].Amount
   135  			})
   136  			if !reflect.DeepEqual(got, tt.want) {
   137  				t.Errorf("subset() = %v, want %v", got, tt.want)
   138  			}
   139  		})
   140  	}
   141  }
   142  
   143  func Fuzz_leastOverFund(f *testing.F) {
   144  	type seed struct {
   145  		amt uint64
   146  		n   int
   147  	}
   148  
   149  	rnd := rand.New(rand.NewSource(1))
   150  
   151  	seeds := make([]seed, 0, 40)
   152  	for i := 0; i < 10; i++ {
   153  		seeds = append(seeds, seed{
   154  			amt: uint64(rnd.Intn(40)),
   155  			n:   rnd.Intn(20000),
   156  		})
   157  	}
   158  
   159  	for _, seed := range seeds {
   160  		f.Add(seed.amt, seed.n)
   161  	}
   162  
   163  	newU := func(amt float64) *CompositeUTXO {
   164  		return &CompositeUTXO{
   165  			UTxO: &UTxO{
   166  				Amount: uint64(amt * 1e8),
   167  			},
   168  			Input: &dexbtc.SpendInfo{},
   169  		}
   170  	}
   171  
   172  	var totalDuration time.Duration
   173  	var totalUTXO int64
   174  
   175  	f.Fuzz(func(t *testing.T, amt uint64, n int) {
   176  		if n < 1 || n > 65535 || amt == 0 || amt > 21e6 {
   177  			t.Skip()
   178  		}
   179  		m := 2 * amt / uint64(n)
   180  		utxos := make([]*CompositeUTXO, n)
   181  		for i := range utxos {
   182  			var v float64
   183  			if rand.Intn(2) > 0 {
   184  				v = rand.Float64()
   185  			}
   186  			if m != 0 {
   187  				v += float64(rand.Int63n(int64(m)))
   188  			}
   189  			if v > 40000 {
   190  				t.Skip()
   191  			}
   192  			utxos[i] = newU(v)
   193  		}
   194  		startTime := time.Now()
   195  		enough := func(_, _, sum uint64) (bool, uint64) {
   196  			return sum >= amt*1e8, 0
   197  		}
   198  		leastOverFund(enough, utxos)
   199  		totalDuration += time.Since(startTime)
   200  		totalUTXO += int64(n)
   201  	})
   202  
   203  	f.Logf("leastOverFund: average duration: %v, with average number of UTXOs: %v\n", totalDuration/100, totalUTXO/100)
   204  }
   205  
   206  func BenchmarkLeastOverFund(b *testing.B) {
   207  	// Same amounts every time.
   208  	rnd := rand.New(rand.NewSource(1))
   209  	utxos := make([]*CompositeUTXO, 2_000)
   210  	for i := range utxos {
   211  		utxo := &CompositeUTXO{
   212  			UTxO: &UTxO{
   213  				Amount: uint64(rnd.Int31n(100) * 1e8),
   214  			},
   215  			Input: &dexbtc.SpendInfo{},
   216  		}
   217  		utxos[i] = utxo
   218  	}
   219  	b.ResetTimer()
   220  	for n := 0; n < b.N; n++ {
   221  		enough := func(_, _, sum uint64) (bool, uint64) {
   222  			return sum >= 10_000*1e8, 0
   223  		}
   224  		leastOverFund(enough, utxos)
   225  	}
   226  }