gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/miningpool/database.go (about)

     1  package pool
     2  
     3  import (
     4  	"bytes"
     5  	"database/sql"
     6  	"errors"
     7  	"fmt"
     8  	"time"
     9  
    10  	"gitlab.com/SiaPrime/SiaPrime/types"
    11  )
    12  
    13  var (
    14  	// ErrDuplicateUserInDifferentCoin is an error when a address used in
    15  	// different coin
    16  	ErrDuplicateUserInDifferentCoin = errors.New("duplicate user in different coin, you need use a different address")
    17  	// ErrNoUsernameInDatabase is an error when can't find a username in db
    18  	ErrNoUsernameInDatabase = errors.New("user is not found in db")
    19  	// ErrCreateClient is an error when can't create a new client
    20  	ErrCreateClient = errors.New("Error when creating a new client")
    21  )
    22  
    23  const (
    24  	sqlReconnectRetry  = 6
    25  	sqlRetryDelay      = 10
    26  	confirmedButUnpaid = "Confirmed but unpaid"
    27  )
    28  
    29  func (p *Pool) newDbConnection() error {
    30  	dbc := p.InternalSettings().PoolDBConnection
    31  	p.dbConnectionMu.Lock()
    32  	defer p.dbConnectionMu.Unlock()
    33  	var err error
    34  
    35  	// to prevent other goroutine reconnect
    36  	if p.sqldb != nil {
    37  		err = p.sqldb.Ping()
    38  		if err == nil {
    39  			return nil
    40  		}
    41  	}
    42  
    43  	for i := 0; i < sqlReconnectRetry; i++ {
    44  		fmt.Printf("try to connect mysql: %d\n", i)
    45  		p.sqldb, err = sql.Open("mysql", dbc)
    46  		if err != nil {
    47  			time.Sleep(sqlRetryDelay * time.Second)
    48  			continue
    49  		}
    50  
    51  		err = p.sqldb.Ping()
    52  		if err != nil {
    53  			time.Sleep(sqlRetryDelay * time.Second)
    54  			continue
    55  		}
    56  		fmt.Printf("success\n")
    57  		return nil
    58  	}
    59  
    60  	return fmt.Errorf("sql reconnect retry time exceeded: %d", sqlReconnectRetry)
    61  }
    62  
    63  // AddClientDB add user into accounts
    64  func (p *Pool) AddClientDB(c *Client) error {
    65  	p.mu.Lock()
    66  	defer func() {
    67  		p.mu.Unlock()
    68  	}()
    69  
    70  	p.yiilog.Printf("Adding user %s to yiimp account\n", c.Name())
    71  	tx, err := p.sqldb.Begin()
    72  	if err != nil {
    73  		return err
    74  	}
    75  	defer tx.Rollback()
    76  
    77  	stmt, err := tx.Prepare(`
    78  		INSERT INTO accounts (coinid, username, coinsymbol)
    79  		VALUES (?, ?, ?);
    80  	`)
    81  	if err != nil {
    82  		return err
    83  	}
    84  	defer stmt.Close()
    85  
    86  	rs, err := stmt.Exec(SiaCoinID, c.cr.name, SiaCoinSymbol)
    87  	if err != nil {
    88  		return err
    89  	}
    90  	err = tx.Commit()
    91  	if err != nil {
    92  		return err
    93  	}
    94  	id, err := rs.LastInsertId()
    95  	p.yiilog.Printf("User %s account id is %d\n", c.Name(), id)
    96  	c.cr.clientID = id
    97  
    98  	return nil
    99  }
   100  
   101  // FindClientDB find user in accounts
   102  func (p *Pool) FindClientDB(name string) (*Client, error) {
   103  	var clientID int64
   104  	var Name, Wallet string
   105  	var coinid int
   106  
   107  	p.yiilog.Debugf("Searching for %s in existing accounts\n", name)
   108  	err := p.sqldb.QueryRow("SELECT id, username, username, coinid FROM accounts WHERE username = ?", name).Scan(&clientID, &Name, &Wallet, &coinid)
   109  	if err != nil {
   110  		p.yiilog.Debugf("Search failed: %s\n", err)
   111  		return nil, ErrNoUsernameInDatabase
   112  	}
   113  	p.yiilog.Debugf("Account %s found: %d \n", Name, clientID)
   114  	if coinid != SiaCoinID {
   115  		p.yiilog.Debugf(ErrDuplicateUserInDifferentCoin.Error())
   116  		return nil, ErrDuplicateUserInDifferentCoin
   117  	}
   118  	// if we're here, we found the client in the database
   119  	// try looking for the client in memory
   120  	c := p.Client(Name)
   121  	// if it's in memory, just return a pointer to the copy in memory
   122  	if c != nil {
   123  		return c, nil
   124  	}
   125  	// client was in database but not in memory -
   126  	// find workers and connect them to the in memory copy
   127  	c, err = newClient(p, name)
   128  	if err != nil {
   129  		p.log.Printf("Error when creating a new client %s: %s\n", name, err)
   130  		return nil, ErrCreateClient
   131  	}
   132  	var wallet types.UnlockHash
   133  	wallet.LoadString(Wallet)
   134  	c.SetWallet(wallet)
   135  	c.cr.clientID = clientID
   136  
   137  	return c, nil
   138  }
   139  
   140  func (w *Worker) deleteWorkerRecord() error {
   141  	stmt, err := w.Parent().pool.sqldb.Prepare(`
   142  		DELETE FROM workers
   143  		WHERE id = ?
   144  	`)
   145  	if err != nil {
   146  		w.log.Printf("Error preparing to update worker: %s\n", err)
   147  		return err
   148  	}
   149  	defer stmt.Close()
   150  	_, err = stmt.Exec(w.wr.workerID)
   151  	if err != nil {
   152  		w.log.Printf("Error deleting record: %s\n", err)
   153  		return err
   154  	}
   155  	return nil
   156  }
   157  
   158  // DeleteAllWorkerRecords deletes all worker records associated with a pool.
   159  // This should be used on pool startup and shutdown to ensure the database
   160  // is clean and isn't storing any worker records for non-connected workers.
   161  func (p *Pool) DeleteAllWorkerRecords() error {
   162  	stmt, err := p.sqldb.Prepare(`
   163  		DELETE FROM workers
   164  		WHERE pid = ?
   165  	`)
   166  	if err != nil {
   167  		p.log.Printf("Error preparing to delete all workers: %s\n", err)
   168  		return err
   169  	}
   170  	defer stmt.Close()
   171  	_, err = stmt.Exec(p.InternalSettings().PoolID)
   172  	if err != nil {
   173  		p.log.Printf("Error deleting records: %s\n", err)
   174  		return err
   175  	}
   176  	return nil
   177  }
   178  
   179  // addFoundBlock add founded block to yiimp blocks table
   180  func (w *Worker) addFoundBlock(b *types.Block) error {
   181  	pool := w.Parent().Pool()
   182  	tx, err := pool.sqldb.Begin()
   183  	if err != nil {
   184  		return err
   185  	}
   186  	defer tx.Rollback()
   187  
   188  	bh := pool.persist.GetBlockHeight()
   189  	w.log.Printf("New block to mine on %d\n", uint64(bh)+1)
   190  	// reward := b.CalculateSubsidy(bh).String()
   191  	pool.mu.Lock()
   192  	defer pool.mu.Unlock()
   193  	timeStamp := time.Now().Unix()
   194  	// TODO: maybe add difficulty_user
   195  	stmt, err := tx.Prepare(`
   196  		INSERT INTO blocks
   197  		(height, blockhash, coin_id, userid, workerid, category, difficulty, time, algo)
   198  		VALUES
   199  		(?, ?, ?, ?, ?, ?, ?, ?, ?)
   200  		`)
   201  	if err != nil {
   202  		return err
   203  	}
   204  	defer stmt.Close()
   205  
   206  	currentTarget, _ := pool.cs.ChildTarget(b.ID())
   207  	difficulty, _ := currentTarget.Difficulty().Uint64() // TODO: maybe should use parent ChildTarget
   208  	// TODO: figure out right difficulty_user
   209  	_, err = stmt.Exec(bh, b.ID().String(), SiaCoinID, w.Parent().cr.clientID,
   210  		w.wr.workerID, "new", difficulty, timeStamp, SiaCoinAlgo)
   211  	if err != nil {
   212  		return err
   213  	}
   214  	err = tx.Commit()
   215  	if err != nil {
   216  		return err
   217  	}
   218  
   219  	return nil
   220  }
   221  
   222  // SaveShift periodically saves the shares for a given worker to the db
   223  func (s *Shift) SaveShift() error {
   224  	if len(s.Shares()) == 0 {
   225  		return nil
   226  	}
   227  
   228  	worker := s.worker
   229  	client := worker.Parent()
   230  	pool := client.Pool()
   231  	var buffer bytes.Buffer
   232  	buffer.WriteString("INSERT INTO shares(userid, workerid, coinid, valid, difficulty, time, algo, reward, block_difficulty, status, height, share_reward, share_diff) VALUES ")
   233  	for i, share := range s.Shares() {
   234  		if i != 0 {
   235  			buffer.WriteString(",")
   236  		}
   237  		buffer.WriteString(fmt.Sprintf("(%d, %d, %d, %t, %f, %d, '%s', %f, %d, %d, %d, %f, %f)",
   238  			share.userid, share.workerid, SiaCoinID, share.valid, share.difficulty, share.time.Unix(),
   239  			SiaCoinAlgo, share.reward, share.blockDifficulty, 0, share.height, share.shareReward, share.shareDifficulty))
   240  	}
   241  	buffer.WriteString(";")
   242  
   243  	rows, err := pool.sqldb.Query(buffer.String())
   244  	if rows != nil {
   245  		rows.Close()
   246  	}
   247  	if err != nil {
   248  		worker.log.Println(buffer.String())
   249  		worker.log.Printf("Error saving shares: %s\n, Try to reconnect", err)
   250  		fmt.Println(err)
   251  		err = pool.newDbConnection()
   252  		if err != nil {
   253  			worker.log.Println(buffer.String())
   254  			worker.log.Printf("Error saving shares: %s\n, Try to reconnect", err)
   255  			fmt.Println(err)
   256  			return err
   257  		}
   258  		rows2, err2 := pool.sqldb.Query(buffer.String())
   259  		if rows2 != nil {
   260  			rows2.Close()
   261  		}
   262  		if err2 != nil {
   263  			worker.log.Println(buffer.String())
   264  			worker.log.Printf("Error adding record of last shift: %s\n", err2)
   265  			return err2
   266  		}
   267  	}
   268  
   269  	return nil
   270  }
   271  
   272  // addWorkerDB inserts info to workers
   273  func (c *Client) addWorkerDB(w *Worker) error {
   274  	c.mu.Lock()
   275  	defer c.mu.Unlock()
   276  
   277  	c.log.Printf("Adding client %s worker %s to database\n", c.cr.name, w.Name())
   278  	tx, err := c.pool.sqldb.Begin()
   279  	if err != nil {
   280  		return err
   281  	}
   282  	defer tx.Rollback()
   283  	// TODO: add ip etc info
   284  	stmt, err := tx.Prepare(`
   285  		INSERT INTO workers (userid, name, worker, algo, time, pid, version, ip)
   286  		VALUES (?, ?, ?, ?, ?, ?, ?, ?);
   287  	`)
   288  	if err != nil {
   289  		return err
   290  	}
   291  	defer stmt.Close()
   292  
   293  	rs, err := stmt.Exec(c.cr.clientID, c.cr.name, w.wr.name, SiaCoinAlgo, time.Now().Unix(),
   294  		c.pool.InternalSettings().PoolID, w.s.clientVersion, w.s.remoteAddr)
   295  	if err != nil {
   296  		return err
   297  	}
   298  
   299  	affectedRows, err := rs.RowsAffected()
   300  	if err != nil {
   301  		return err
   302  	}
   303  	w.log.Printf("Rows affected insert workers %d", affectedRows)
   304  
   305  	id, err := rs.LastInsertId()
   306  	if err != nil {
   307  		return err
   308  	}
   309  
   310  	w.wr.workerID = id
   311  
   312  	err = tx.Commit()
   313  	if err != nil {
   314  		return err
   315  	}
   316  
   317  	return nil
   318  }