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