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 }