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