github.com/NebulousLabs/Sia@v1.3.7/modules/miner/miner_test.go (about) 1 package miner 2 3 import ( 4 "bytes" 5 "path/filepath" 6 "testing" 7 "time" 8 9 "github.com/NebulousLabs/Sia/build" 10 "github.com/NebulousLabs/Sia/crypto" 11 "github.com/NebulousLabs/Sia/modules" 12 "github.com/NebulousLabs/Sia/modules/consensus" 13 "github.com/NebulousLabs/Sia/modules/gateway" 14 "github.com/NebulousLabs/Sia/modules/transactionpool" 15 "github.com/NebulousLabs/Sia/modules/wallet" 16 "github.com/NebulousLabs/Sia/types" 17 "github.com/NebulousLabs/fastrand" 18 ) 19 20 // A minerTester is the helper object for miner testing. 21 type minerTester struct { 22 gateway modules.Gateway 23 cs modules.ConsensusSet 24 tpool modules.TransactionPool 25 wallet modules.Wallet 26 walletKey crypto.TwofishKey 27 28 miner *Miner 29 30 minedBlocks []types.Block 31 persistDir string 32 } 33 34 // createMinerTester creates a minerTester that's ready for use. 35 func createMinerTester(name string) (*minerTester, error) { 36 testdir := build.TempDir(modules.MinerDir, name) 37 38 // Create the modules. 39 g, err := gateway.New("localhost:0", false, filepath.Join(testdir, modules.GatewayDir)) 40 if err != nil { 41 return nil, err 42 } 43 cs, err := consensus.New(g, false, filepath.Join(testdir, modules.ConsensusDir)) 44 if err != nil { 45 return nil, err 46 } 47 tp, err := transactionpool.New(cs, g, filepath.Join(testdir, modules.TransactionPoolDir)) 48 if err != nil { 49 return nil, err 50 } 51 w, err := wallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir)) 52 if err != nil { 53 return nil, err 54 } 55 var key crypto.TwofishKey 56 fastrand.Read(key[:]) 57 _, err = w.Encrypt(key) 58 if err != nil { 59 return nil, err 60 } 61 err = w.Unlock(key) 62 if err != nil { 63 return nil, err 64 } 65 m, err := New(cs, tp, w, filepath.Join(testdir, modules.MinerDir)) 66 if err != nil { 67 return nil, err 68 } 69 70 // Assemble the minerTester. 71 mt := &minerTester{ 72 gateway: g, 73 cs: cs, 74 tpool: tp, 75 wallet: w, 76 walletKey: key, 77 78 miner: m, 79 80 persistDir: testdir, 81 } 82 83 // Mine until the wallet has money. 84 for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ { 85 b, err := m.AddBlock() 86 if err != nil { 87 return nil, err 88 } 89 mt.minedBlocks = append(mt.minedBlocks, b) 90 } 91 92 return mt, nil 93 } 94 95 // TestIntegrationMiner creates a miner, mines a few blocks, and checks that 96 // the wallet balance is updating as the blocks get mined. 97 func TestIntegrationMiner(t *testing.T) { 98 if testing.Short() { 99 t.SkipNow() 100 } 101 mt, err := createMinerTester(t.Name()) 102 if err != nil { 103 t.Fatal(err) 104 } 105 106 // Check that the wallet has money. 107 siacoins, _, _, err := mt.wallet.ConfirmedBalance() 108 if err != nil { 109 t.Error(err) 110 } 111 if siacoins.IsZero() { 112 t.Error("expecting mining full balance to not be zero") 113 } 114 115 // Mine a bunch of blocks. 116 for i := 0; i < 50; i++ { 117 b, _ := mt.miner.FindBlock() 118 err = mt.cs.AcceptBlock(b) 119 if err != nil { 120 t.Fatal(err) 121 } 122 } 123 morecoins, _, _, err := mt.wallet.ConfirmedBalance() 124 if err != nil { 125 t.Error(err) 126 } 127 if siacoins.Cmp(morecoins) >= 0 { 128 t.Error("wallet is not gaining balance while mining") 129 } 130 } 131 132 // TestIntegrationNilMinerDependencies tests that the miner properly handles 133 // nil inputs for its dependencies. 134 func TestIntegrationNilMinerDependencies(t *testing.T) { 135 if testing.Short() { 136 t.SkipNow() 137 } 138 mt, err := createMinerTester(t.Name()) 139 if err != nil { 140 t.Fatal(err) 141 } 142 _, err = New(mt.cs, mt.tpool, nil, "") 143 if err != errNilWallet { 144 t.Fatal(err) 145 } 146 _, err = New(mt.cs, nil, mt.wallet, "") 147 if err != errNilTpool { 148 t.Fatal(err) 149 } 150 _, err = New(nil, mt.tpool, mt.wallet, "") 151 if err != errNilCS { 152 t.Fatal(err) 153 } 154 _, err = New(nil, nil, nil, "") 155 if err == nil { 156 t.Fatal(err) 157 } 158 } 159 160 // TestIntegrationBlocksMined checks that the BlocksMined function correctly 161 // indicates the number of real blocks and stale blocks that have been mined. 162 func TestIntegrationBlocksMined(t *testing.T) { 163 if testing.Short() { 164 t.SkipNow() 165 } 166 mt, err := createMinerTester(t.Name()) 167 if err != nil { 168 t.Fatal(err) 169 } 170 171 // Get an unsolved header. 172 unsolvedHeader, target, err := mt.miner.HeaderForWork() 173 if err != nil { 174 t.Fatal(err) 175 } 176 // Unsolve the header - necessary because the target is very low when 177 // mining. 178 for { 179 unsolvedHeader.Nonce[0]++ 180 id := crypto.HashObject(unsolvedHeader) 181 if bytes.Compare(target[:], id[:]) < 0 { 182 break 183 } 184 } 185 186 // Get two solved headers. 187 header1, target, err := mt.miner.HeaderForWork() 188 if err != nil { 189 t.Fatal(err) 190 } 191 header1 = solveHeader(header1, target) 192 header2, target, err := mt.miner.HeaderForWork() 193 if err != nil { 194 t.Fatal(err) 195 } 196 header2 = solveHeader(header2, target) 197 198 // Submit the unsolved header followed by the two solved headers, this 199 // should result in 1 real block mined and 1 stale block mined. 200 err = mt.miner.SubmitHeader(unsolvedHeader) 201 if err != modules.ErrBlockUnsolved { 202 t.Fatal(err) 203 } 204 err = mt.miner.SubmitHeader(header1) 205 if err != nil { 206 t.Fatal(err) 207 } 208 err = mt.miner.SubmitHeader(header2) 209 if err != modules.ErrNonExtendingBlock { 210 t.Fatal(err) 211 } 212 goodBlocks, staleBlocks := mt.miner.BlocksMined() 213 if goodBlocks != 1 { 214 t.Error("expecting 1 good block") 215 } 216 if staleBlocks != 1 { 217 t.Error("expecting 1 stale block, got", staleBlocks) 218 } 219 220 // Reboot the miner and verify that the block record has persisted. 221 err = mt.miner.Close() 222 if err != nil { 223 t.Fatal(err) 224 } 225 rebootMiner, err := New(mt.cs, mt.tpool, mt.wallet, filepath.Join(mt.persistDir, modules.MinerDir)) 226 if err != nil { 227 t.Fatal(err) 228 } 229 goodBlocks, staleBlocks = rebootMiner.BlocksMined() 230 if goodBlocks != 1 { 231 t.Error("expecting 1 good block") 232 } 233 if staleBlocks != 1 { 234 t.Error("expecting 1 stale block, got", staleBlocks) 235 } 236 } 237 238 // TestIntegrationAutoRescan triggers a rescan during a call to New and 239 // verifies that the rescanning happens correctly. The rescan is triggered by 240 // a call to New, instead of getting called directly. 241 func TestIntegrationAutoRescan(t *testing.T) { 242 if testing.Short() { 243 t.SkipNow() 244 } 245 mt, err := createMinerTester(t.Name()) 246 if err != nil { 247 t.Fatal(err) 248 } 249 _, err = mt.miner.AddBlock() 250 if err != nil { 251 t.Fatal(err) 252 } 253 254 // Get the persist data of the current miner. 255 oldChange := mt.miner.persist.RecentChange 256 oldHeight := mt.miner.persist.Height 257 oldTarget := mt.miner.persist.Target 258 259 // Corrupt the miner, close the miner, and make a new one from the same 260 // directory. 261 mt.miner.persist.RecentChange[0]++ 262 mt.miner.persist.Height += 1e5 263 mt.miner.persist.Target[0]++ 264 err = mt.miner.Close() // miner saves when it closes. 265 if err != nil { 266 t.Fatal(err) 267 } 268 269 // Verify that rescanning resolved the corruption in the miner. 270 m, err := New(mt.cs, mt.tpool, mt.wallet, filepath.Join(mt.persistDir, modules.MinerDir)) 271 if err != nil { 272 t.Fatal(err) 273 } 274 // Check that after rescanning, the values have returned to the usual values. 275 if m.persist.RecentChange != oldChange { 276 t.Error("rescan failed, ended up on the wrong change") 277 } 278 if m.persist.Height != oldHeight { 279 t.Error("rescan failed, ended up at the wrong height") 280 } 281 if m.persist.Target != oldTarget { 282 t.Error("rescan failed, ended up at the wrong target") 283 } 284 } 285 286 // TestIntegrationStartupRescan probes the startupRescan function, checking 287 // that it works in the naive case. Rescan is called directly. 288 func TestIntegrationStartupRescan(t *testing.T) { 289 if testing.Short() { 290 t.SkipNow() 291 } 292 mt, err := createMinerTester(t.Name()) 293 if err != nil { 294 t.Fatal(err) 295 } 296 297 // Check that the miner's persist variables have been initialized to the 298 // first few blocks. 299 if mt.miner.persist.RecentChange == (modules.ConsensusChangeID{}) || mt.miner.persist.Height == 0 || mt.miner.persist.Target == (types.Target{}) { 300 t.Fatal("miner persist variables not initialized") 301 } 302 oldChange := mt.miner.persist.RecentChange 303 oldHeight := mt.miner.persist.Height 304 oldTarget := mt.miner.persist.Target 305 306 // Corrupt the miner and verify that a rescan repairs the corruption. 307 mt.miner.persist.RecentChange[0]++ 308 mt.miner.persist.Height += 500 309 mt.miner.persist.Target[0]++ 310 mt.cs.Unsubscribe(mt.miner) 311 err = mt.miner.startupRescan() 312 if err != nil { 313 t.Fatal(err) 314 } 315 if mt.miner.persist.RecentChange != oldChange { 316 t.Error("rescan failed, ended up on the wrong change") 317 } 318 if mt.miner.persist.Height != oldHeight { 319 t.Error("rescan failed, ended up at the wrong height") 320 } 321 if mt.miner.persist.Target != oldTarget { 322 t.Error("rescan failed, ended up at the wrong target") 323 } 324 } 325 326 // TestMinerCloseDeadlock checks that the miner can cleanly close even if the 327 // CPU miner is running. 328 func TestMinerCloseDeadlock(t *testing.T) { 329 if testing.Short() { 330 t.SkipNow() 331 } 332 mt, err := createMinerTester(t.Name()) 333 if err != nil { 334 t.Fatal(err) 335 } 336 // StartCPUMining calls `go threadedMine()`, which needs to access the miner 337 // before Close() does in the next goroutine, otherwise m.tg.Add() fails 338 // at the top of threadedMine() and threadedMine() exits (silently!). 339 // I haven't seen this behavior since sticking Close() inside a goroutine, 340 // but I'm not sure that's comfort enough. 341 mt.miner.StartCPUMining() 342 time.Sleep(time.Millisecond * 250) 343 344 closed := make(chan struct{}) 345 go func() { 346 if err := mt.miner.Close(); err != nil { 347 t.Fatal(err) 348 } 349 closed <- struct{}{} 350 }() 351 select { 352 case <-closed: 353 case <-time.After(5 * time.Second): 354 t.Fatal("mt.miner.Close never completed") 355 } 356 }