gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/wallet/defrag_test.go (about) 1 package wallet 2 3 import ( 4 "fmt" 5 "testing" 6 "time" 7 8 "gitlab.com/NebulousLabs/errors" 9 "gitlab.com/SiaPrime/SiaPrime/build" 10 "gitlab.com/SiaPrime/SiaPrime/modules" 11 "gitlab.com/SiaPrime/SiaPrime/types" 12 ) 13 14 // TestDefragWallet mines many blocks and checks that the wallet's outputs are 15 // consolidated once more than defragThreshold blocks are mined. 16 func TestDefragWallet(t *testing.T) { 17 if testing.Short() { 18 t.SkipNow() 19 } 20 t.Parallel() 21 wt, err := createWalletTester(t.Name(), modules.ProdDependencies) 22 if err != nil { 23 t.Fatal(err) 24 } 25 defer wt.closeWt() 26 27 // mine defragThreshold blocks, resulting in defragThreshold outputs 28 for i := 0; i < defragThreshold; i++ { 29 _, err := wt.miner.AddBlock() 30 if err != nil { 31 t.Fatal(err) 32 } 33 } 34 35 // add another block to push the number of outputs over the threshold 36 _, err = wt.miner.AddBlock() 37 if err != nil { 38 t.Fatal(err) 39 } 40 41 // allow some time for the defrag transaction to occur, then mine another block 42 time.Sleep(time.Second * 5) 43 44 _, err = wt.miner.AddBlock() 45 if err != nil { 46 t.Fatal(err) 47 } 48 49 // defrag should keep the outputs below the threshold 50 wt.wallet.mu.Lock() 51 // force a sync because bucket stats may not be reliable until commit 52 wt.wallet.syncDB() 53 siacoinOutputs := wt.wallet.dbTx.Bucket(bucketSiacoinOutputs).Stats().KeyN 54 wt.wallet.mu.Unlock() 55 if siacoinOutputs > defragThreshold { 56 t.Fatalf("defrag should result in fewer than defragThreshold outputs, got %v wanted %v\n", siacoinOutputs, defragThreshold) 57 } 58 } 59 60 // TestDefragWalletDust verifies that dust outputs do not trigger the defrag 61 // operation. 62 func TestDefragWalletDust(t *testing.T) { 63 if testing.Short() { 64 t.SkipNow() 65 } 66 t.Parallel() 67 wt, err := createWalletTester(t.Name(), modules.ProdDependencies) 68 if err != nil { 69 t.Fatal(err) 70 } 71 defer wt.closeWt() 72 73 dustOutputValue := types.NewCurrency64(10000) 74 noutputs := defragThreshold + 1 75 76 tbuilder, err := wt.wallet.StartTransaction() 77 if err != nil { 78 t.Fatal(err) 79 } 80 err = tbuilder.FundSiacoins(dustOutputValue.Mul64(uint64(noutputs))) 81 if err != nil { 82 t.Fatal(err) 83 } 84 85 wt.wallet.mu.Lock() 86 var dest types.UnlockHash 87 for k := range wt.wallet.keys { 88 dest = k 89 break 90 } 91 wt.wallet.mu.Unlock() 92 93 for i := 0; i < noutputs; i++ { 94 tbuilder.AddSiacoinOutput(types.SiacoinOutput{ 95 Value: dustOutputValue, 96 UnlockHash: dest, 97 }) 98 } 99 100 txns, err := tbuilder.Sign(true) 101 if err != nil { 102 t.Fatal(err) 103 } 104 105 err = wt.tpool.AcceptTransactionSet(txns) 106 if err != nil { 107 t.Fatal(err) 108 } 109 110 _, err = wt.miner.AddBlock() 111 if err != nil { 112 t.Fatal(err) 113 } 114 115 time.Sleep(time.Second) 116 117 wt.wallet.mu.Lock() 118 // force a sync because bucket stats may not be reliable until commit 119 wt.wallet.syncDB() 120 siacoinOutputs := wt.wallet.dbTx.Bucket(bucketSiacoinOutputs).Stats().KeyN 121 wt.wallet.mu.Unlock() 122 if siacoinOutputs < defragThreshold { 123 t.Fatal("defrag consolidated dust outputs") 124 } 125 } 126 127 // TestDefragOutputExhaustion verifies that sending transactions still succeeds 128 // even when the defragger is under heavy stress. 129 func TestDefragOutputExhaustion(t *testing.T) { 130 if testing.Short() || !build.VLONG { 131 t.SkipNow() 132 } 133 t.Parallel() 134 wt, err := createWalletTester(t.Name(), modules.ProdDependencies) 135 if err != nil { 136 t.Fatal(err) 137 } 138 defer wt.closeWt() 139 140 wt.wallet.mu.Lock() 141 var dest types.UnlockHash 142 for k := range wt.wallet.keys { 143 dest = k 144 break 145 } 146 wt.wallet.mu.Unlock() 147 148 _, err = wt.miner.AddBlock() 149 if err != nil { 150 t.Fatal(err) 151 } 152 153 // concurrently make a bunch of transactions with lots of outputs to keep the 154 // defragger running 155 closechan := make(chan struct{}) 156 donechan := make(chan struct{}) 157 go func() { 158 defer close(donechan) 159 for { 160 select { 161 case <-closechan: 162 return 163 case <-time.After(time.Millisecond * 100): 164 _, err := wt.miner.AddBlock() 165 if err != nil { 166 t.Fatal(err) 167 } 168 txnValue := types.SiacoinPrecision.Mul64(3000) 169 fee := types.SiacoinPrecision.Mul64(10) 170 numOutputs := defragThreshold + 1 171 172 tbuilder, err := wt.wallet.StartTransaction() 173 if err != nil { 174 t.Fatal(err) 175 } 176 177 tbuilder.FundSiacoins(txnValue.Mul64(uint64(numOutputs)).Add(fee)) 178 179 for i := 0; i < numOutputs; i++ { 180 tbuilder.AddSiacoinOutput(types.SiacoinOutput{ 181 Value: txnValue, 182 UnlockHash: dest, 183 }) 184 } 185 186 tbuilder.AddMinerFee(fee) 187 188 txns, err := tbuilder.Sign(true) 189 if err != nil { 190 t.Error("Error signing fragmenting transaction:", err) 191 } 192 err = wt.tpool.AcceptTransactionSet(txns) 193 if err != nil { 194 t.Error("Error accepting fragmenting transaction:", err) 195 } 196 _, err = wt.miner.AddBlock() 197 if err != nil { 198 t.Fatal(err) 199 } 200 } 201 } 202 }() 203 204 time.Sleep(time.Second * 1) 205 206 // ensure we can still send transactions while receiving aggressively 207 // fragmented outputs 208 for i := 0; i < 30; i++ { 209 sendAmount := types.SiacoinPrecision.Mul64(2000) 210 _, err = wt.wallet.SendSiacoins(sendAmount, types.UnlockHash{}) 211 if err != nil { 212 t.Errorf("%v: %v", i, err) 213 } 214 time.Sleep(time.Millisecond * 50) 215 } 216 217 close(closechan) 218 <-donechan 219 } 220 221 // TestDefragInterrupted checks that a failing defrag un-marks spent outputs correctly 222 func TestDefragInterrupted(t *testing.T) { 223 if testing.Short() { 224 t.SkipNow() 225 } 226 t.Parallel() 227 deps := dependencyDefragInterrupted{} 228 deps.fail() 229 wt, err := createWalletTester(t.Name(), &deps) 230 if err != nil { 231 t.Fatal(err) 232 } 233 defer wt.closeWt() 234 235 // mine defragThreshold blocks, resulting in defragThreshold outputs 236 for i := 0; i < defragThreshold; i++ { 237 _, err := wt.miner.AddBlock() 238 if err != nil { 239 t.Fatal(err) 240 } 241 } 242 243 err = build.Retry(50, 100*time.Millisecond, func() error { 244 wt.wallet.mu.Lock() 245 // force a sync because bucket stats may not be reliable until commit 246 wt.wallet.syncDB() 247 spentOutputs := wt.wallet.dbTx.Bucket(bucketSpentOutputs).Stats().KeyN 248 siacoinOutputs := wt.wallet.dbTx.Bucket(bucketSiacoinOutputs).Stats().KeyN 249 wt.wallet.mu.Unlock() 250 251 if siacoinOutputs <= defragThreshold { 252 return errors.New("not enough outputs created - defrag wasn't triggered") 253 } 254 if spentOutputs > 0 { 255 err := fmt.Errorf("There should be 0 outputs in the database since defrag failed but there were %v", 256 spentOutputs) 257 return err 258 } 259 return nil 260 }) 261 if err != nil { 262 t.Fatal(err) 263 } 264 265 // Trigger defrag again 266 _, err = wt.miner.AddBlock() 267 if err != nil { 268 t.Fatal(err) 269 } 270 271 // Check to make sure wallet is updated 272 loop := 0 273 err = build.Retry(50, 100*time.Millisecond, func() error { 274 // Mine another block every 10 iterations to make sure the wallet is 275 // updated 276 if loop%10 == 0 { 277 _, err = wt.miner.AddBlock() 278 if err != nil { 279 t.Fatal(err) 280 } 281 } 282 loop++ 283 284 // force a sync because bucket stats may not be reliable until commit 285 wt.wallet.mu.Lock() 286 wt.wallet.syncDB() 287 spentOutputs := wt.wallet.dbTx.Bucket(bucketSpentOutputs).Stats().KeyN 288 siacoinOutputs := wt.wallet.dbTx.Bucket(bucketSiacoinOutputs).Stats().KeyN 289 wt.wallet.mu.Unlock() 290 291 if siacoinOutputs > defragThreshold { 292 err := fmt.Errorf("defrag should result in fewer than defragThreshold outputs, got %v wanted %v", siacoinOutputs, defragThreshold) 293 return err 294 } 295 if spentOutputs == 0 { 296 return errors.New("There should be > 0 spentOutputs") 297 } 298 return nil 299 }) 300 if err != nil { 301 t.Fatal(err) 302 } 303 }