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  }