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

     1  // Package pool is an implementation of the pool module, and is responsible for
     2  // creating a mining pool, accepting incoming potential block solutions and
     3  // rewarding the submitters proportionally for their shares.
     4  package pool
     5  
     6  import (
     7  	"bytes"
     8  	"database/sql"
     9  	"encoding/binary"
    10  	"encoding/hex"
    11  	"errors"
    12  	"fmt"
    13  	"math/rand"
    14  	"net"
    15  	"path/filepath"
    16  	"sync/atomic"
    17  	"time"
    18  
    19  	"github.com/sasha-s/go-deadlock"
    20  
    21  	"gitlab.com/NebulousLabs/threadgroup"
    22  	"gitlab.com/SiaPrime/SiaPrime/build"
    23  	"gitlab.com/SiaPrime/SiaPrime/config"
    24  	"gitlab.com/SiaPrime/SiaPrime/crypto"
    25  	"gitlab.com/SiaPrime/SiaPrime/modules"
    26  	"gitlab.com/SiaPrime/SiaPrime/persist"
    27  	"gitlab.com/SiaPrime/SiaPrime/types"
    28  
    29  	// blank to load the sql driver for mysql
    30  	_ "github.com/go-sql-driver/mysql"
    31  )
    32  
    33  var (
    34  	// persistMetadata is the header that gets written to the persist file, and is
    35  	// used to recognize other persist files.
    36  	persistMetadata = persist.Metadata{
    37  		Header:  "Sia Pool",
    38  		Version: "0.0.1",
    39  	}
    40  
    41  	// errPoolClosed gets returned when a call is rejected due to the pool
    42  	// having been closed.
    43  	errPoolClosed = errors.New("call is disabled because the pool is closed")
    44  
    45  	// Nil dependency errors.
    46  	errNilCS    = errors.New("pool cannot use a nil consensus state")
    47  	errNilTpool = errors.New("pool cannot use a nil transaction pool")
    48  	errNilGW    = errors.New("pool cannot use a nil gateway")
    49  	//	errNilWallet = errors.New("pool cannot use a nil wallet")
    50  
    51  	// Required settings to run pool
    52  	errNoAddressSet = errors.New("pool operators address must be set")
    53  
    54  	running  bool  // indicates if the mining pool is actually running
    55  	hashRate int64 // indicates hashes per second
    56  	// HeaderMemory is the number of previous calls to 'header'
    57  	// that are remembered. Additionally, 'header' will only poll for a
    58  	// new block every 'headerMemory / blockMemory' times it is
    59  	// called. This reduces the amount of memory used, but comes at the cost of
    60  	// not always having the most recent transactions.
    61  	HeaderMemory = build.Select(build.Var{
    62  		Standard: 10000,
    63  		Dev:      500,
    64  		Testing:  50,
    65  	}).(int)
    66  
    67  	// BlockMemory is the maximum number of blocks the miner will store
    68  	// Blocks take up to 2 megabytes of memory, which is why this number is
    69  	// limited.
    70  	BlockMemory = build.Select(build.Var{
    71  		Standard: 50,
    72  		Dev:      10,
    73  		Testing:  5,
    74  	}).(int)
    75  
    76  	// MaxSourceBlockAge is the maximum amount of time that is allowed to
    77  	// elapse between generating source blocks.
    78  	MaxSourceBlockAge = build.Select(build.Var{
    79  		Standard: 30 * time.Second,
    80  		Dev:      5 * time.Second,
    81  		Testing:  1 * time.Second,
    82  	}).(time.Duration)
    83  
    84  	// ShiftDuration is how often we commit mining data to persistent
    85  	// storage when a block hasn't been found.
    86  	ShiftDuration = build.Select(build.Var{
    87  		Standard: 20 * time.Second,
    88  		Dev:      20 * time.Second,
    89  		Testing:  1 * time.Second,
    90  	}).(time.Duration)
    91  )
    92  
    93  // splitSet defines a transaction set that can be added componenet-wise to a
    94  // block. It's split because it doesn't necessarily represent the full set
    95  // prpovided by the transaction pool. Splits can be sorted so that the largest
    96  // and most valuable sets can be selected when picking transactions.
    97  type splitSet struct {
    98  	averageFee   types.Currency
    99  	size         uint64
   100  	transactions []types.Transaction
   101  }
   102  
   103  type splitSetID int
   104  
   105  // A Pool contains all the fields necessary for storing status for clients and
   106  // performing the evaluation and rewarding on submitted shares
   107  type Pool struct {
   108  	// BlockManager variables. Becaues blocks are large, one block is used to
   109  	// make many headers which can be used by miners. Headers include an
   110  	// arbitrary data transaction (appended to the block) to make the merkle
   111  	// roots unique (preventing miners from doing redundant work). Every N
   112  	// requests or M seconds, a new block is used to create headers.
   113  	//
   114  	// Only 'blocksMemory' blocks are kept in memory at a time, which
   115  	// keeps ram usage reasonable. Miners may request many headers in parallel,
   116  	// and thus may be working on different blocks. When they submit the solved
   117  	// header to the block manager, the rest of the block needs to be found in
   118  	// a lookup.
   119  	blockMem        map[types.BlockHeader]*types.Block             // Mappings from headers to the blocks they are derived from.
   120  	blockTxns       *txnList                                       // list of transactions that are supposed to be solved in the next block
   121  	arbDataMem      map[types.BlockHeader][crypto.EntropySize]byte // Mappings from the headers to their unique arb data.
   122  	headerMem       []types.BlockHeader                            // A circular list of headers that have been given out from the api recently.
   123  	sourceBlock     types.Block                                    // The block from which new headers for mining are created.
   124  	sourceBlockTime time.Time                                      // How long headers have been using the same block (different from 'recent block').
   125  	memProgress     int                                            // The index of the most recent header used in headerMem.
   126  
   127  	// Transaction pool variables.
   128  	fullSets        map[modules.TransactionSetID][]int
   129  	blockMapHeap    *mapHeap
   130  	overflowMapHeap *mapHeap
   131  	setCounter      int
   132  	splitSets       map[splitSetID]*splitSet
   133  
   134  	// Dependencies.
   135  	cs     modules.ConsensusSet
   136  	tpool  modules.TransactionPool
   137  	wallet modules.Wallet
   138  	gw     modules.Gateway
   139  	dependencies
   140  	modules.StorageManager
   141  
   142  	// Pool ACID fields - these fields need to be updated in serial, ACID
   143  	// transactions.
   144  	announceConfirmed bool
   145  	secretKey         crypto.SecretKey
   146  	// Pool transient fields - these fields are either determined at startup or
   147  	// otherwise are not critical to always be correct.
   148  	workingStatus        modules.PoolWorkingStatus
   149  	connectabilityStatus modules.PoolConnectabilityStatus
   150  
   151  	// Utilities.
   152  	sqldb          *sql.DB
   153  	listener       net.Listener
   154  	log            *persist.Logger
   155  	yiilog         *persist.Logger
   156  	mu             deadlock.RWMutex
   157  	dbConnectionMu deadlock.RWMutex
   158  	persistDir     string
   159  	port           string
   160  	tg             threadgroup.ThreadGroup
   161  	persist        persistence
   162  	dispatcher     *Dispatcher
   163  	stratumID      uint64
   164  	shiftID        uint64
   165  	shiftChan      chan bool
   166  	shiftTimestamp time.Time
   167  	clients        map[string]*Client //client name to client pointer mapping
   168  
   169  	clientSetupMutex deadlock.Mutex
   170  	runningMutex     deadlock.RWMutex
   171  	running          bool
   172  }
   173  
   174  // startupRescan will rescan the blockchain in the event that the pool
   175  // persistence layer has become desynchronized from the consensus persistence
   176  // layer. This might happen if a user replaces any of the folders with backups
   177  // or deletes any of the folders.
   178  func (p *Pool) startupRescan() error {
   179  	// Reset all of the variables that have relevance to the consensus set. The
   180  	// operations are wrapped by an anonymous function so that the locking can
   181  	// be handled using a defer statement.
   182  	err := func() error {
   183  		//		p.log.Debugf("Waiting to lock pool\n")
   184  		p.mu.Lock()
   185  		defer func() {
   186  			//			p.log.Debugf("Unlocking pool\n")
   187  			p.mu.Unlock()
   188  		}()
   189  
   190  		p.log.Println("Performing a pool rescan.")
   191  		p.persist.SetRecentChange(modules.ConsensusChangeBeginning)
   192  		p.persist.SetBlockHeight(0)
   193  		p.persist.SetTarget(types.Target{})
   194  		return p.saveSync()
   195  	}()
   196  	if err != nil {
   197  		return err
   198  	}
   199  
   200  	// Subscribe to the consensus set. This is a blocking call that will not
   201  	// return until the pool has fully caught up to the current block.
   202  	err = p.cs.ConsensusSetSubscribe(p, modules.ConsensusChangeBeginning, p.tg.StopChan())
   203  	if err != nil {
   204  		return err
   205  	}
   206  	p.tg.OnStop(func() error {
   207  		p.cs.Unsubscribe(p)
   208  		return nil
   209  	})
   210  	return nil
   211  }
   212  
   213  func (p *Pool) monitorShifts() {
   214  	p.shiftChan = make(chan bool, 1)
   215  	p.tg.Add()
   216  	defer p.tg.Done()
   217  	for {
   218  		select {
   219  		case <-p.shiftChan:
   220  		case <-time.After(ShiftDuration):
   221  		case <-p.tg.StopChan():
   222  			return
   223  		}
   224  		p.log.Debugf("Shift change - end of shift %d\n", p.shiftID)
   225  		atomic.AddUint64(&p.shiftID, 1)
   226  		p.dispatcher.mu.RLock()
   227  		// TODO: switch to batched insert
   228  		for _, h := range p.dispatcher.handlers {
   229  			h.mu.RLock()
   230  			s := h.s.Shift()
   231  			h.mu.RUnlock()
   232  			go func(savingShift *Shift) {
   233  				if savingShift != nil {
   234  					savingShift.SaveShift()
   235  				}
   236  			}(s)
   237  			sh := p.newShift(h.s.CurrentWorker)
   238  			h.s.addShift(sh)
   239  		}
   240  		p.dispatcher.mu.RUnlock()
   241  	}
   242  }
   243  
   244  func (p *Pool) startServer() {
   245  	p.log.Printf("      Waiting for consensus synchronization...\n")
   246  	select {
   247  	// if we've received the stop message before this can even be spun up, just exit
   248  	case <-p.tg.StopChan():
   249  		return
   250  	default:
   251  	}
   252  	p.tg.Add()
   253  	defer p.tg.Done()
   254  	for {
   255  		if p.cs.Synced() {
   256  			// If we're immediately synced upon subscription AND we never got ProcessConsensusChange
   257  			// calls (this happens when we start the server and our most recent change was the latest
   258  			// block in a synced chain), we will need to look at the top of the chain to set up our
   259  			// source block. So let's just always do that upon first sync.
   260  			finalBlock := p.cs.CurrentBlock()
   261  			parentID := finalBlock.ID()
   262  			p.mu.Lock()
   263  			p.newSourceBlock()
   264  			p.sourceBlock.ParentID = parentID
   265  			p.sourceBlock.Timestamp, _ = p.cs.MinimumValidChildTimestamp(parentID)
   266  			p.mu.Unlock()
   267  
   268  			p.log.Printf("      Starting Stratum Server\n")
   269  
   270  			port := fmt.Sprintf("%d", p.InternalSettings().PoolNetworkPort)
   271  			go p.dispatcher.ListenHandlers(port)
   272  			p.tg.OnStop(func() error {
   273  				if p.dispatcher.ln == nil {
   274  					//panic(errors.New("network not opened yet"))
   275  				} else {
   276  					p.dispatcher.ln.Close()
   277  				}
   278  				return nil
   279  			})
   280  			return
   281  		}
   282  		time.Sleep(100 * time.Millisecond)
   283  	}
   284  }
   285  
   286  // newPool returns an initialized Pool, taking a set of dependencies as input.
   287  // By making the dependencies an argument of the 'new' call, the pool can be
   288  // mocked such that the dependencies can return unexpected errors or unique
   289  // behaviors during testing, enabling easier testing of the failure modes of
   290  // the Pool.
   291  func newPool(dependencies dependencies, cs modules.ConsensusSet, tpool modules.TransactionPool, gw modules.Gateway, wallet modules.Wallet, persistDir string, initConfig config.MiningPoolConfig) (*Pool, error) {
   292  	// Check that all the dependencies were provided.
   293  	if cs == nil {
   294  		return nil, errNilCS
   295  	}
   296  	if tpool == nil {
   297  		return nil, errNilTpool
   298  	}
   299  	if gw == nil {
   300  		return nil, errNilGW
   301  	}
   302  
   303  	// Create the pool object.
   304  	p := &Pool{
   305  		cs:           cs,
   306  		tpool:        tpool,
   307  		gw:           gw,
   308  		wallet:       wallet,
   309  		dependencies: dependencies,
   310  
   311  		blockMem:   make(map[types.BlockHeader]*types.Block),
   312  		blockTxns:  newTxnList(),
   313  		arbDataMem: make(map[types.BlockHeader][crypto.EntropySize]byte),
   314  		headerMem:  make([]types.BlockHeader, HeaderMemory),
   315  
   316  		fullSets:  make(map[modules.TransactionSetID][]int),
   317  		splitSets: make(map[splitSetID]*splitSet),
   318  		blockMapHeap: &mapHeap{
   319  			selectID: make(map[splitSetID]*mapElement),
   320  			data:     nil,
   321  			minHeap:  true,
   322  		},
   323  		overflowMapHeap: &mapHeap{
   324  			selectID: make(map[splitSetID]*mapElement),
   325  			data:     nil,
   326  			minHeap:  false,
   327  		},
   328  
   329  		persistDir: persistDir,
   330  		stratumID:  rand.Uint64(),
   331  		clients:    make(map[string]*Client),
   332  	}
   333  	var err error
   334  
   335  	// Create the perist directory if it does not yet exist.
   336  	err = dependencies.mkdirAll(p.persistDir, 0700)
   337  	if err != nil {
   338  		return nil, err
   339  	}
   340  
   341  	// Initialize the logger, and set up the stop call that will close the
   342  	// logger.
   343  	// fmt.Println("log path:", filepath.Join(p.persistDir, logFile))
   344  	p.log, err = dependencies.newLogger(filepath.Join(p.persistDir, logFile))
   345  	if err != nil {
   346  		return nil, err
   347  	}
   348  	p.yiilog, err = dependencies.newLogger(filepath.Join(p.persistDir, yiilogFile))
   349  	if err != nil {
   350  		return nil, err
   351  	}
   352  	p.tg.AfterStop(func() error {
   353  		err = p.log.Close()
   354  		if err != nil {
   355  			// State of the logger is uncertain, a Println will have to
   356  			// suffice.
   357  			fmt.Println("Error when closing the logger:", err)
   358  		}
   359  		return err
   360  	})
   361  
   362  	// Load the prior persistence structures, and configure the pool to save
   363  	// before shutting down.
   364  	err = p.load()
   365  	if err != nil {
   366  		return nil, err
   367  	}
   368  	p.setPoolSettings(initConfig)
   369  
   370  	p.tg.AfterStop(func() error {
   371  		p.mu.Lock()
   372  		err = p.saveSync()
   373  		p.mu.Unlock()
   374  		if err != nil {
   375  			p.log.Println("Could not save pool upon shutdown:", err)
   376  		}
   377  		return err
   378  	})
   379  
   380  	p.newDbConnection()
   381  
   382  	// clean old worker records for this stratum server just in case we didn't
   383  	// shutdown cleanly
   384  	err = p.DeleteAllWorkerRecords()
   385  	if err != nil {
   386  		return nil, errors.New("Failed to clean database: " + err.Error())
   387  	}
   388  
   389  	p.tg.OnStop(func() error {
   390  		p.DeleteAllWorkerRecords()
   391  		p.sqldb.Close()
   392  		return nil
   393  	})
   394  
   395  	// grab our consensus set data
   396  	err = p.cs.ConsensusSetSubscribe(p, p.persist.RecentChange, p.tg.StopChan())
   397  	if err == modules.ErrInvalidConsensusChangeID {
   398  		// Perform a rescan of the consensus set if the change id is not found.
   399  		// The id will only be not found if there has been desynchronization
   400  		// between the miner and the consensus package.
   401  		err = p.startupRescan()
   402  		if err != nil {
   403  			return nil, errors.New("mining pool startup failed - rescanning failed: " + err.Error())
   404  		}
   405  	} else if err != nil {
   406  		return nil, errors.New("mining pool subscription failed: " + err.Error())
   407  	}
   408  
   409  	// spin up a go routine to handle shift changes.
   410  	go p.monitorShifts()
   411  
   412  	p.tg.OnStop(func() error {
   413  		p.cs.Unsubscribe(p)
   414  		return nil
   415  	})
   416  
   417  	p.tpool.TransactionPoolSubscribe(p)
   418  	p.tg.OnStop(func() error {
   419  		p.tpool.Unsubscribe(p)
   420  		return nil
   421  	})
   422  
   423  	p.runningMutex.Lock()
   424  	p.dispatcher = &Dispatcher{handlers: make(map[string]*Handler), mu: deadlock.RWMutex{}, p: p}
   425  	p.dispatcher.log, _ = dependencies.newLogger(filepath.Join(p.persistDir, "stratum.log"))
   426  	p.running = true
   427  	p.runningMutex.Unlock()
   428  
   429  	go p.startServer()
   430  
   431  	return p, nil
   432  }
   433  
   434  // New returns an initialized Pool.
   435  func New(cs modules.ConsensusSet, tpool modules.TransactionPool, gw modules.Gateway, wallet modules.Wallet, persistDir string, initConfig config.MiningPoolConfig) (*Pool, error) {
   436  	return newPool(productionDependencies{}, cs, tpool, gw, wallet, persistDir, initConfig)
   437  }
   438  
   439  // Close shuts down the pool.
   440  func (p *Pool) Close() error {
   441  	p.log.Println("Closing pool")
   442  	//defer func () {}()
   443  	return p.tg.Stop()
   444  }
   445  
   446  // SetInternalSettings updates the pool's internal PoolInternalSettings object.
   447  func (p *Pool) SetInternalSettings(settings modules.PoolInternalSettings) error {
   448  	p.mu.Lock()
   449  	defer p.mu.Unlock()
   450  
   451  	err := p.tg.Add()
   452  	if err != nil {
   453  		return err
   454  	}
   455  	defer p.tg.Done()
   456  
   457  	// The pool should not be open for business if it does not have an
   458  	// unlock hash.
   459  	err = p.checkAddress()
   460  	if err != nil {
   461  		return errors.New("internal settings not updated, no operator wallet set: " + err.Error())
   462  	}
   463  
   464  	p.persist.SetSettings(settings)
   465  	p.persist.SetRevisionNumber(p.persist.GetRevisionNumber() + 1)
   466  
   467  	err = p.saveSync()
   468  	if err != nil {
   469  		return errors.New("internal settings updated, but failed saving to disk: " + err.Error())
   470  	}
   471  	return nil
   472  }
   473  
   474  // InternalSettings returns the settings of a pool.
   475  func (p *Pool) InternalSettings() modules.PoolInternalSettings {
   476  	return p.persist.GetSettings()
   477  }
   478  
   479  // checkAddress checks that the miner has an address, fetching an address from
   480  // the wallet if not.
   481  func (p *Pool) checkAddress() error {
   482  	if p.InternalSettings().PoolWallet == (types.UnlockHash{}) {
   483  		return errNoAddressSet
   484  	}
   485  	return nil
   486  }
   487  
   488  // Client returns the client with the specified name that has been stored in
   489  // memory
   490  func (p *Pool) Client(name string) *Client {
   491  	p.mu.RLock()
   492  	defer p.mu.RUnlock()
   493  
   494  	return p.clients[name]
   495  }
   496  
   497  // AddClient stores the client with the specified name into memory
   498  func (p *Pool) AddClient(c *Client) {
   499  	p.mu.Lock()
   500  	defer p.mu.Unlock()
   501  
   502  	p.clients[c.Name()] = c
   503  
   504  }
   505  
   506  // newStratumID returns a function pointer to a unique ID generator used
   507  // for more the unique IDs within the Stratum protocol
   508  func (p *Pool) newStratumID() (f func() uint64) {
   509  	f = func() uint64 {
   510  		// p.log.Debugf("Waiting to lock pool\n")
   511  		p.mu.Lock()
   512  		defer func() {
   513  			// p.log.Debugf("Unlocking pool\n")
   514  			p.mu.Unlock()
   515  		}()
   516  		atomic.AddUint64(&p.stratumID, 1)
   517  		return p.stratumID
   518  	}
   519  	return
   520  }
   521  
   522  func (p *Pool) coinB1() types.Transaction {
   523  	s := fmt.Sprintf("\000     Software: siad-miningpool-module v%d.%02d\nPool name: \"%s\"     \000", MajorVersion, MinorVersion, p.InternalSettings().PoolName)
   524  	if ((len(modules.PrefixNonSia[:]) + len(s)) % 2) != 0 {
   525  		// odd length, add extra null
   526  		s = s + "\000"
   527  	}
   528  	cb := make([]byte, len(modules.PrefixNonSia[:])+len(s)) // represents the bytes appended later
   529  	n := copy(cb, modules.PrefixNonSia[:])
   530  	copy(cb[n:], s)
   531  	return types.Transaction{
   532  		ArbitraryData: [][]byte{cb},
   533  	}
   534  }
   535  
   536  func (p *Pool) coinB1Txn() string {
   537  	coinbaseTxn := p.coinB1()
   538  	buf := new(bytes.Buffer)
   539  	coinbaseTxn.MarshalSiaNoSignatures(buf)
   540  	b := buf.Bytes()
   541  	// extranonce1 and extranonce2 are 4 bytes each, and they will be part of the
   542  	// arbitrary transaction via the arbitrary data field. when the arbitrary data
   543  	// field is marshalled, the length of the arbitrary data must be specified. thus we
   544  	// leave 8 bytes for the necessary 2 extranonce fields. The position here is determined
   545  	// by the format specified in the MarshalSiaNoSignatures function.
   546  	binary.LittleEndian.PutUint64(b[72:87], binary.LittleEndian.Uint64(b[72:87])+8)
   547  	return hex.EncodeToString(b)
   548  }
   549  
   550  func (p *Pool) coinB2() string {
   551  	return "0000000000000000"
   552  }
   553  
   554  // NumConnections returns the number of tcp connections from clients the pool
   555  // currently has open
   556  func (p *Pool) NumConnections() int {
   557  	p.runningMutex.RLock()
   558  	defer p.runningMutex.RUnlock()
   559  	if p.running {
   560  		return p.dispatcher.NumConnections()
   561  	}
   562  	return 0
   563  }
   564  
   565  // NumConnectionsOpened returns the total number of tcp connections from clients the
   566  // pool has opened since startup
   567  func (p *Pool) NumConnectionsOpened() uint64 {
   568  	p.runningMutex.RLock()
   569  	defer p.runningMutex.RUnlock()
   570  	if p.running {
   571  		return p.dispatcher.NumConnectionsOpened()
   572  	}
   573  	return 0
   574  }