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