gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/miningpool/miningpool_test.go (about) 1 package pool 2 3 import ( 4 "database/sql" 5 "fmt" 6 "net" 7 "path/filepath" 8 "strconv" 9 "testing" 10 "time" 11 12 "gitlab.com/NebulousLabs/fastrand" 13 "gitlab.com/SiaPrime/SiaPrime/build" 14 fileConfig "gitlab.com/SiaPrime/SiaPrime/config" 15 "gitlab.com/SiaPrime/SiaPrime/crypto" 16 "gitlab.com/SiaPrime/SiaPrime/modules" 17 "gitlab.com/SiaPrime/SiaPrime/modules/consensus" 18 "gitlab.com/SiaPrime/SiaPrime/modules/gateway" 19 "gitlab.com/SiaPrime/SiaPrime/modules/stratumminer" 20 "gitlab.com/SiaPrime/SiaPrime/modules/transactionpool" 21 "gitlab.com/SiaPrime/SiaPrime/modules/wallet" 22 "gitlab.com/SiaPrime/SiaPrime/types" 23 24 "gitlab.com/NebulousLabs/errors" 25 26 _ "github.com/go-sql-driver/mysql" 27 ) 28 29 const ( 30 tdbUser = "miningpool_test" 31 tdbPass = "miningpool_test" 32 tdbAddress = "127.0.0.1" 33 tdbPort = "3306" 34 tdbName = "miningpool_test" 35 tPoolWallet = "6f24f36a94c052ea0f706e03914c693dc8c668bd1bf844690c221e98439b5e8b0a11711977da" 36 37 tAddress = "b197ffe829605b03d6ba478fff68a7ac59788ca9174f8a8a5d13ba9ce0c251e9db553643b15a" 38 tUser = "tester" 39 tID = uint64(123) 40 ) 41 42 type poolTester struct { 43 gateway modules.Gateway 44 cs modules.ConsensusSet 45 tpool modules.TransactionPool 46 wallet modules.Wallet 47 walletKey crypto.TwofishKey 48 49 mpool *Pool 50 51 minedBlocks []types.Block 52 persistDir string 53 } 54 55 func GetFreePort() (int, error) { 56 addr, err := net.ResolveTCPAddr("tcp", "localhost:0") 57 if err != nil { 58 return 0, err 59 } 60 61 l, err := net.ListenTCP("tcp", addr) 62 if err != nil { 63 return 0, err 64 } 65 defer l.Close() 66 return l.Addr().(*net.TCPAddr).Port, nil 67 } 68 69 func newPoolTester(name string, port int) (*poolTester, error) { 70 fmt.Printf("newPoolTester: %s, port %d\n", name, port) 71 testdir := build.TempDir(modules.PoolDir, name) 72 fmt.Printf("temp path: %s\n", testdir) 73 74 // Create the modules. 75 g, err := gateway.New("localhost:0", false, filepath.Join(testdir, modules.GatewayDir)) 76 if err != nil { 77 return nil, err 78 } 79 cs, err := consensus.New(g, false, filepath.Join(testdir, modules.ConsensusDir)) 80 if err != nil { 81 return nil, err 82 } 83 tp, err := transactionpool.New(cs, g, filepath.Join(testdir, modules.TransactionPoolDir)) 84 if err != nil { 85 return nil, err 86 } 87 w, err := wallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir)) 88 if err != nil { 89 return nil, err 90 } 91 var key crypto.TwofishKey 92 fastrand.Read(key[:]) 93 _, err = w.Encrypt(key) 94 if err != nil { 95 return nil, err 96 } 97 err = w.Unlock(key) 98 if err != nil { 99 return nil, err 100 } 101 102 // sql to create user: CREATE USER 'miningpool_test'@'localhost' IDENTIFIED BY 'miningpool_test';GRANT ALL PRIVILEGES ON *.* TO 'miningpool_test'@'localhost' WITH GRANT OPTION;CREATE USER 'miningpool_test'@'%' IDENTIFIED BY 'miningpool_test';GRANT ALL PRIVILEGES ON *.* TO 'miningpool_test'@'%' WITH GRANT OPTION;flush privileges; 103 // 104 createPoolDBConnection := fmt.Sprintf("%s:%s@tcp(%s:%s)/", tdbUser, tdbPass, tdbAddress, tdbPort) 105 err = createPoolDatabase(createPoolDBConnection, tdbName) 106 if err != nil { 107 return nil, err 108 } 109 110 if port == 0 { 111 port, err = GetFreePort() 112 if err != nil { 113 return nil, err 114 } 115 } 116 117 poolConfig := fileConfig.MiningPoolConfig{ 118 PoolNetworkPort: port, 119 PoolName: "miningpool_test", 120 PoolID: 99, 121 PoolDBConnection: fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", tdbUser, tdbPass, tdbAddress, tdbPort, tdbName), 122 PoolWallet: tPoolWallet, 123 } 124 125 mpool, err := New(cs, tp, g, w, filepath.Join(testdir, modules.PoolDir), poolConfig) 126 127 if err != nil { 128 return nil, err 129 } 130 131 // Assemble the poolTester. 132 pt := &poolTester{ 133 gateway: g, 134 cs: cs, 135 tpool: tp, 136 wallet: w, 137 walletKey: key, 138 139 mpool: mpool, 140 141 persistDir: testdir, 142 } 143 144 return pt, nil 145 } 146 147 func createPoolDatabase(connection string, dbname string) error { 148 sqldb, err := sql.Open("mysql", connection) 149 if err != nil { 150 return err 151 } 152 153 tx, err := sqldb.Begin() 154 if err != nil { 155 return err 156 } 157 defer tx.Rollback() 158 159 _, err = tx.Exec(fmt.Sprintf("DROP DATABASE %s;", dbname)) 160 // it's ok if we can't drop it if it is not exists 161 162 _, err = tx.Exec(fmt.Sprintf("CREATE DATABASE %s;", dbname)) 163 if err != nil { 164 return err 165 } 166 _, err = tx.Exec(fmt.Sprintf("USE %s;", dbname)) 167 if err != nil { 168 return err 169 } 170 _, err = tx.Exec(` 171 CREATE TABLE accounts ( 172 id int(255) NOT NULL AUTO_INCREMENT, 173 coinid int(11) DEFAULT NULL, 174 last_earning int(10) DEFAULT NULL, 175 is_locked tinyint(1) DEFAULT '0', 176 no_fees tinyint(1) DEFAULT NULL, 177 donation tinyint(3) unsigned NOT NULL DEFAULT '0', 178 logtraffic tinyint(1) DEFAULT NULL, 179 balance double DEFAULT '0', 180 username varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, 181 coinsymbol varchar(16) DEFAULT NULL, 182 swap_time int(10) unsigned DEFAULT NULL, 183 login varchar(45) DEFAULT NULL, 184 hostaddr varchar(39) DEFAULT NULL, 185 PRIMARY KEY (id), 186 UNIQUE KEY username (username), 187 KEY coin (coinid), 188 KEY balance (balance), 189 KEY earning (last_earning) 190 ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 191 `) 192 if err != nil { 193 return err 194 } 195 _, err = tx.Exec(` 196 CREATE TABLE workers ( 197 id int(11) NOT NULL AUTO_INCREMENT, 198 userid int(11) DEFAULT NULL, 199 time int(11) DEFAULT NULL, 200 pid int(11) DEFAULT NULL, 201 subscribe tinyint(1) DEFAULT NULL, 202 difficulty double DEFAULT NULL, 203 ip varchar(32) DEFAULT NULL, 204 dns varchar(1024) DEFAULT NULL, 205 name varchar(128) DEFAULT NULL, 206 nonce1 varchar(64) DEFAULT NULL, 207 version varchar(64) DEFAULT NULL, 208 password varchar(64) DEFAULT NULL, 209 worker varchar(64) DEFAULT NULL, 210 algo varchar(16) DEFAULT 'scrypt', 211 PRIMARY KEY (id), 212 KEY algo1 (algo), 213 KEY name1 (name), 214 KEY userid (userid), 215 KEY pid (pid) 216 ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 217 `) 218 if err != nil { 219 return err 220 } 221 _, err = tx.Exec(` 222 CREATE TABLE blocks ( 223 id int(11) unsigned NOT NULL AUTO_INCREMENT, 224 coin_id int(11) DEFAULT NULL, 225 height int(11) unsigned DEFAULT NULL, 226 confirmations int(11) DEFAULT NULL, 227 time int(11) DEFAULT NULL, 228 userid int(11) DEFAULT NULL, 229 workerid int(11) DEFAULT NULL, 230 difficulty_user double DEFAULT NULL, 231 price double DEFAULT NULL, 232 amount double DEFAULT NULL, 233 difficulty double DEFAULT NULL, 234 category varchar(16) DEFAULT NULL, 235 algo varchar(16) DEFAULT 'scrypt', 236 blockhash varchar(128) DEFAULT NULL, 237 txhash varchar(128) DEFAULT NULL, 238 segwit tinyint(1) unsigned NOT NULL DEFAULT '0', 239 PRIMARY KEY (id), 240 KEY time (time), 241 KEY algo1 (algo), 242 KEY coin (coin_id), 243 KEY category (category), 244 KEY user1 (userid), 245 KEY height1 (height) 246 ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 247 `) 248 if err != nil { 249 return err 250 } 251 _, err = tx.Exec(` 252 CREATE TABLE shares ( 253 id bigint(30) NOT NULL AUTO_INCREMENT, 254 userid int(11) DEFAULT NULL, 255 workerid int(11) DEFAULT NULL, 256 coinid int(11) DEFAULT NULL, 257 jobid int(11) DEFAULT NULL, 258 pid int(11) DEFAULT NULL, 259 time int(11) DEFAULT NULL, 260 error int(11) DEFAULT NULL, 261 valid tinyint(1) DEFAULT NULL, 262 extranonce1 tinyint(1) DEFAULT NULL, 263 difficulty double NOT NULL DEFAULT '0', 264 share_diff double NOT NULL DEFAULT '0', 265 algo varchar(16) DEFAULT 'x11', 266 reward double DEFAULT NULL, 267 block_difficulty double DEFAULT NULL, 268 status int(11) DEFAULT NULL, 269 height int(11) DEFAULT NULL, 270 share_reward double DEFAULT NULL, 271 PRIMARY KEY (id), 272 KEY time (time), 273 KEY algo1 (algo), 274 KEY valid1 (valid), 275 KEY user1 (userid), 276 KEY worker1 (workerid), 277 KEY coin1 (coinid), 278 KEY jobid (jobid) 279 ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 280 `) 281 if err != nil { 282 return err 283 } 284 err = tx.Commit() 285 if err != nil { 286 return err 287 } 288 return nil 289 } 290 291 // Close safely closes the hostTester. It panics if err != nil because there 292 // isn't a good way to errcheck when deferring a close. 293 func (pt *poolTester) Close() error { 294 // note: order of closing modules is important! 295 // we have to unsubscribe the pool module from the tpool before we can close the tpool 296 //fmt.Println("poolTester Close()") 297 var errs []error 298 //fmt.Println("closing mpool") 299 errs = append(errs, pt.mpool.Close()) 300 //fmt.Println("closing tpool") 301 errs = append(errs, pt.tpool.Close()) 302 //fmt.Println("closing cs") 303 errs = append(errs, pt.cs.Close()) 304 //fmt.Println("closing gateway") 305 errs = append(errs, pt.gateway.Close()) 306 if err := build.JoinErrors(errs, "; "); err != nil { 307 panic(err) 308 } 309 //fmt.Println("finished poolTester Close()") 310 return nil 311 } 312 313 func TestCreatingTestPool(t *testing.T) { 314 if !build.POOL { 315 return 316 } 317 pt, err := newPoolTester(t.Name(), 0) 318 defer pt.Close() 319 if err != nil { 320 t.Fatal(err) 321 } 322 if pt.cs.Height() != 0 { 323 t.Fatal(errors.New("new consensus height wrong")) 324 } 325 time.Sleep(time.Millisecond * 2) 326 } 327 328 // test starting and stopping a miner rapidly on the pool using a bad address: 329 // good for catching race conditions 330 func TestStratumStartStopMiningBadAddress(t *testing.T) { 331 if !build.POOL { 332 return 333 } 334 pt, err := newPoolTester("TestStratumStartStopMiningBadAddress", 0) 335 defer pt.Close() 336 if err != nil { 337 t.Fatal(err) 338 } 339 testdir := build.TempDir(modules.PoolDir, "TestStratumStartStopMiningBadAddressStratumMiner") 340 sm, err := stratumminer.New(testdir) 341 if err != nil { 342 t.Fatal(err) 343 } 344 settings := pt.mpool.InternalSettings() 345 port := strconv.FormatInt(int64(settings.PoolNetworkPort), 10) 346 username := "foo" 347 sm.StartStratumMining(port, username) 348 sm.StopStratumMining() 349 sm.StartStratumMining(port, username) 350 sm.StopStratumMining() 351 } 352 353 // test starting and stopping a miner rapidly on the pool using a valid address: 354 // good for catching race conditions 355 func TestStratumStartStopMiningGoodAddress(t *testing.T) { 356 if !build.POOL { 357 return 358 } 359 pt, err := newPoolTester("TestStratumStartStopMiningGoodAddress", 0) 360 defer pt.Close() 361 if err != nil { 362 t.Fatal(err) 363 } 364 testdir := build.TempDir(modules.PoolDir, "TestStratumStartStopMiningGoodAddressStratumMiner") 365 sm, err := stratumminer.New(testdir) 366 if err != nil { 367 t.Fatal(err) 368 } 369 settings := pt.mpool.InternalSettings() 370 port := strconv.FormatInt(int64(settings.PoolNetworkPort), 10) 371 username := "06cc9f9196afb1a1efa21f72160d508f0cc192b581770fde57420cab795a2913fb4e1c85aa30" 372 url := fmt.Sprintf("stratum+tcp://localhost:%s", port) 373 sm.StartStratumMining(url, username) 374 sm.StopStratumMining() 375 sm.StartStratumMining(url, username) 376 sm.StopStratumMining() 377 } 378 379 func TestStratumMineBlocks(t *testing.T) { 380 if !build.POOL { 381 return 382 } 383 pt, err := newPoolTester("TestStratumMineBlocks", 0) 384 defer pt.Close() 385 if err != nil { 386 t.Fatal(err) 387 } 388 389 testdir := build.TempDir(modules.PoolDir, "TestStratumMineBlocksStratumMiner") 390 sm, err := stratumminer.New(testdir) 391 if err != nil { 392 t.Fatal(err) 393 } 394 settings := pt.mpool.InternalSettings() 395 port := strconv.FormatInt(int64(settings.PoolNetworkPort), 10) 396 username := "06cc9f9196afb1a1efa21f72160d508f0cc192b581770fde57420cab795a2913fb4e1c85aa30" 397 url := fmt.Sprintf("stratum+tcp://localhost:%s", port) 398 sm.StartStratumMining(url, username) 399 time.Sleep(time.Millisecond * 20) 400 if !sm.Connected() { 401 t.Fatal(errors.New("stratum server is running, but we are not connected")) 402 } 403 time.Sleep(time.Millisecond * 20) 404 if !sm.Mining() { 405 t.Fatal(errors.New("stratum server is running and we are connected, but we are not mining")) 406 } 407 time.Sleep(time.Millisecond * 10000) 408 if sm.Hashrate() == 0 { 409 t.Fatal(errors.New("we've been mining for a while but hashrate is 0")) 410 } 411 if sm.Submissions() == 0 { 412 t.Fatal(errors.New("we've been mining for a while but have no submissions")) 413 } 414 sm.StopStratumMining() 415 } 416 417 // NOTE: this is a long test 418 func TestStratumMineBlocksMiningUncleanShutdown(t *testing.T) { 419 if !build.POOL { 420 return 421 } 422 pt, err := newPoolTester("TestStratumMineBlocksMiningUncleanShutdown", 0) 423 if err != nil { 424 t.Fatal(err) 425 } 426 if pt != nil { 427 defer pt.Close() 428 testdir := build.TempDir(modules.PoolDir, "TestStratumMineBlocksMiningUncleanShutdownMiner") 429 sm, err := stratumminer.New(testdir) 430 if err != nil { 431 t.Fatal(err) 432 } 433 settings := pt.mpool.InternalSettings() 434 port := strconv.FormatInt(int64(settings.PoolNetworkPort), 10) 435 username := "06cc9f9196afb1a1efa21f72160d508f0cc192b581770fde57420cab795a2913fb4e1c85aa30" 436 url := fmt.Sprintf("stratum+tcp://localhost:%s", port) 437 sm.StartStratumMining(url, username) 438 time.Sleep(time.Millisecond * 20) 439 if !sm.Connected() { 440 t.Fatal(errors.New("stratum server is running, but we are not connected")) 441 } 442 // don't stop mining, just let the server shutdown 443 } 444 } 445 446 func TestStratumMiningWhileRestart(t *testing.T) { 447 if !build.POOL { 448 return 449 } 450 pt, err := newPoolTester(t.Name(), 0) 451 if err != nil { 452 t.Fatal(err) 453 } 454 if pt != nil { 455 time.Sleep(time.Millisecond * 2) 456 settings := pt.mpool.InternalSettings() 457 port := strconv.FormatInt(int64(settings.PoolNetworkPort), 10) 458 username := "06cc9f9196afb1a1efa21f72160d508f0cc192b581770fde57420cab795a2913fb4e1c85aa30" 459 url := fmt.Sprintf("stratum+tcp://localhost:%s", port) 460 testdir := build.TempDir(modules.PoolDir, "TestStratumMineBlocksMiningUncleanShutdownMiner") 461 sm, err := stratumminer.New(testdir) 462 if err != nil { 463 t.Fatal(err) 464 } 465 sm.StartStratumMining(url, username) 466 time.Sleep(time.Millisecond * 20) 467 if !sm.Connected() { 468 t.Fatal(errors.New("stratum server is running, but we are not connected")) 469 } 470 time.Sleep(time.Millisecond * 20) 471 if !sm.Mining() { 472 t.Fatal(errors.New("stratum server is running and we are connected, but we are not mining")) 473 } 474 time.Sleep(time.Millisecond * 10000) 475 if sm.Hashrate() == 0 { 476 t.Fatal(errors.New("we've been mining for a while but hashrate is 0")) 477 } 478 if sm.Submissions() == 0 { 479 t.Fatal(errors.New("we've been mining for a while but have no submissions")) 480 } 481 pt.Close() 482 time.Sleep(time.Millisecond * 200) 483 if sm.Connected() { 484 t.Fatal(errors.New("stratum server is closed, but we are connected")) 485 } 486 pt, err = newPoolTester(t.Name(), settings.PoolNetworkPort) 487 time.Sleep(time.Millisecond * 5000) 488 if !sm.Connected() { 489 t.Fatal(errors.New("stratum server is restarted and running, but we are not connected")) 490 } 491 sm.StopStratumMining() 492 } 493 }