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