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