github.com/ZuluSpl0it/Sia@v1.3.7/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.ProdDependencies) 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.ProdDependencies) 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, err := wt.wallet.StartTransaction() 75 if err != nil { 76 t.Fatal(err) 77 } 78 err = tbuilder.FundSiacoins(dustOutputValue.Mul64(uint64(noutputs))) 79 if err != nil { 80 t.Fatal(err) 81 } 82 83 wt.wallet.mu.Lock() 84 var dest types.UnlockHash 85 for k := range wt.wallet.keys { 86 dest = k 87 break 88 } 89 wt.wallet.mu.Unlock() 90 91 for i := 0; i < noutputs; i++ { 92 tbuilder.AddSiacoinOutput(types.SiacoinOutput{ 93 Value: dustOutputValue, 94 UnlockHash: dest, 95 }) 96 } 97 98 txns, err := tbuilder.Sign(true) 99 if err != nil { 100 t.Fatal(err) 101 } 102 103 err = wt.tpool.AcceptTransactionSet(txns) 104 if err != nil { 105 t.Fatal(err) 106 } 107 108 _, err = wt.miner.AddBlock() 109 if err != nil { 110 t.Fatal(err) 111 } 112 113 time.Sleep(time.Second) 114 115 wt.wallet.mu.Lock() 116 // force a sync because bucket stats may not be reliable until commit 117 wt.wallet.syncDB() 118 siacoinOutputs := wt.wallet.dbTx.Bucket(bucketSiacoinOutputs).Stats().KeyN 119 wt.wallet.mu.Unlock() 120 if siacoinOutputs < defragThreshold { 121 t.Fatal("defrag consolidated dust outputs") 122 } 123 } 124 125 // TestDefragOutputExhaustion verifies that sending transactions still succeeds 126 // even when the defragger is under heavy stress. 127 func TestDefragOutputExhaustion(t *testing.T) { 128 if testing.Short() || !build.VLONG { 129 t.SkipNow() 130 } 131 t.Parallel() 132 wt, err := createWalletTester(t.Name(), modules.ProdDependencies) 133 if err != nil { 134 t.Fatal(err) 135 } 136 defer wt.closeWt() 137 138 wt.wallet.mu.Lock() 139 var dest types.UnlockHash 140 for k := range wt.wallet.keys { 141 dest = k 142 break 143 } 144 wt.wallet.mu.Unlock() 145 146 _, err = wt.miner.AddBlock() 147 if err != nil { 148 t.Fatal(err) 149 } 150 151 // concurrently make a bunch of transactions with lots of outputs to keep the 152 // defragger running 153 closechan := make(chan struct{}) 154 donechan := make(chan struct{}) 155 go func() { 156 defer close(donechan) 157 for { 158 select { 159 case <-closechan: 160 return 161 case <-time.After(time.Millisecond * 100): 162 _, err := wt.miner.AddBlock() 163 if err != nil { 164 t.Fatal(err) 165 } 166 txnValue := types.SiacoinPrecision.Mul64(3000) 167 fee := types.SiacoinPrecision.Mul64(10) 168 numOutputs := defragThreshold + 1 169 170 tbuilder, err := wt.wallet.StartTransaction() 171 if err != nil { 172 t.Fatal(err) 173 } 174 175 tbuilder.FundSiacoins(txnValue.Mul64(uint64(numOutputs)).Add(fee)) 176 177 for i := 0; i < numOutputs; i++ { 178 tbuilder.AddSiacoinOutput(types.SiacoinOutput{ 179 Value: txnValue, 180 UnlockHash: dest, 181 }) 182 } 183 184 tbuilder.AddMinerFee(fee) 185 186 txns, err := tbuilder.Sign(true) 187 if err != nil { 188 t.Error("Error signing fragmenting transaction:", err) 189 } 190 err = wt.tpool.AcceptTransactionSet(txns) 191 if err != nil { 192 t.Error("Error accepting fragmenting transaction:", err) 193 } 194 _, err = wt.miner.AddBlock() 195 if err != nil { 196 t.Fatal(err) 197 } 198 } 199 } 200 }() 201 202 time.Sleep(time.Second * 1) 203 204 // ensure we can still send transactions while receiving aggressively 205 // fragmented outputs 206 for i := 0; i < 30; i++ { 207 sendAmount := types.SiacoinPrecision.Mul64(2000) 208 _, err = wt.wallet.SendSiacoins(sendAmount, types.UnlockHash{}) 209 if err != nil { 210 t.Errorf("%v: %v", i, err) 211 } 212 time.Sleep(time.Millisecond * 50) 213 } 214 215 close(closechan) 216 <-donechan 217 } 218 219 // TestDefragInterrupted checks that a failing defrag unmarks spent outputs correctly 220 func TestDefragInterrupted(t *testing.T) { 221 if testing.Short() { 222 t.SkipNow() 223 } 224 t.Parallel() 225 deps := dependencyDefragInterrupted{} 226 deps.fail() 227 wt, err := createWalletTester(t.Name(), &deps) 228 if err != nil { 229 t.Fatal(err) 230 } 231 defer wt.closeWt() 232 233 // mine defragThreshold blocks, resulting in defragThreshold outputs 234 for i := 0; i < defragThreshold; i++ { 235 _, err := wt.miner.AddBlock() 236 if err != nil { 237 t.Fatal(err) 238 } 239 } 240 241 // allow some time for the defrag transaction to occur, then mine another block 242 time.Sleep(time.Second * 5) 243 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 if siacoinOutputs <= defragThreshold { 251 t.Fatal("not enough outputs created - defrag wasn't triggered") 252 } 253 if spentOutputs > 0 { 254 t.Fatalf("There should be 0 outputs in the database since defrag failed but there were %v", 255 spentOutputs) 256 } 257 258 // Trigger defrag again 259 _, err = wt.miner.AddBlock() 260 if err != nil { 261 t.Fatal(err) 262 } 263 264 // allow some time for the defrag 265 time.Sleep(time.Second * 5) 266 267 // Mine another block to update the wallet 268 _, err = wt.miner.AddBlock() 269 if err != nil { 270 t.Fatal(err) 271 } 272 273 // Defrag should have worked this time 274 wt.wallet.mu.Lock() 275 // force a sync because bucket stats may not be reliable until commit 276 wt.wallet.syncDB() 277 spentOutputs = wt.wallet.dbTx.Bucket(bucketSpentOutputs).Stats().KeyN 278 siacoinOutputs = wt.wallet.dbTx.Bucket(bucketSiacoinOutputs).Stats().KeyN 279 wt.wallet.mu.Unlock() 280 if siacoinOutputs > defragThreshold { 281 t.Fatalf("defrag should result in fewer than defragThreshold outputs, got %v wanted %v\n", siacoinOutputs, defragThreshold) 282 } 283 if spentOutputs == 0 { 284 t.Fatalf("There should be > 0 spentOutputs") 285 } 286 287 }