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

     1  package stratumminer
     2  
     3  import (
     4  	"errors"
     5  	"log"
     6  	"math/big"
     7  	"reflect"
     8  	//"strconv"
     9  	"sync"
    10  	"time"
    11  
    12  	"gitlab.com/SiaPrime/SiaPrime/build"
    13  	"gitlab.com/SiaPrime/SiaPrime/crypto"
    14  	"gitlab.com/SiaPrime/SiaPrime/encoding"
    15  	"gitlab.com/SiaPrime/SiaPrime/types"
    16  
    17  	siasync "gitlab.com/SiaPrime/SiaPrime/sync"
    18  )
    19  
    20  type stratumJob struct {
    21  	JobID        string
    22  	PrevHash     []byte
    23  	Coinbase1    []byte
    24  	Coinbase2    []byte
    25  	MerkleBranch [][]byte
    26  	Version      string
    27  	NBits        string
    28  	NTime        []byte
    29  	CleanJobs    bool
    30  	ExtraNonce2  types.ExtraNonce2
    31  }
    32  
    33  //StratumClient is a sia client using the stratum protocol
    34  type StratumClient struct {
    35  	connectionstring string
    36  	User             string
    37  
    38  	mutex           sync.Mutex // protects following
    39  	tcpclient       *TcpClient
    40  	tcpinited       bool
    41  	shouldstop      bool
    42  	extranonce1     []byte
    43  	extranonce2Size uint
    44  	target          types.Target
    45  	currentJob      stratumJob
    46  
    47  	tg siasync.ThreadGroup
    48  	BaseClient
    49  }
    50  
    51  // RestartOnError closes the old tcpclient and starts the process of opening
    52  // a new tcp connection
    53  func (sc *StratumClient) RestartOnError(err error) {
    54  	if err := sc.tg.Add(); err != nil {
    55  		//build.Critical(err)
    56  		// this code is reachable if we stopped before we fully started up
    57  		return
    58  	}
    59  	defer sc.tg.Done()
    60  	log.Println("Error in connection to stratumserver:", err)
    61  	select {
    62  	case <-sc.tg.StopChan():
    63  		log.Println("... tried restarting stratumclient from error, but stop has already been called. exiting")
    64  		return
    65  	default:
    66  	}
    67  	log.Println("blocking on mutex")
    68  	sc.mutex.Lock()
    69  	log.Println("done blocking on mutex")
    70  	sc.tcpinited = false
    71  	sc.mutex.Unlock()
    72  	sc.DeprecateOutstandingJobs()
    73  	log.Println("done deprecating outstanding jobs")
    74  	sc.tcpclient.Close()
    75  
    76  	log.Println("  Closed tcpclient")
    77  	time.Sleep(1000 * time.Millisecond)
    78  	// Make sure we haven't called Stop(). If we haven't, try again.
    79  	log.Println("... Restarting stratumclient from error")
    80  	sc.Start()
    81  }
    82  
    83  // SubscribeAndAuthorize first attempts to authorize and, if successful,
    84  // subscribes
    85  func (sc *StratumClient) SubscribeAndAuthorize() {
    86  	// Subscribe for mining
    87  	// Closing the connection on an error will cause the client to generate an error,
    88  	// resulting in the errorhandler to be triggered
    89  	result, err := sc.tcpclient.Call("mining.subscribe", []string{"test-gominer"})
    90  	if err != nil {
    91  		log.Println("subscribe not ok")
    92  		log.Println("ERROR Error in response from stratum:", err)
    93  		return
    94  	}
    95  	log.Println("subscribe ok")
    96  	reply, ok := result.([]interface{})
    97  	if !ok || len(reply) < 3 {
    98  		log.Println("ERROR Invalid response from stratum:", result)
    99  		sc.tcpclient.Close()
   100  		return
   101  	}
   102  
   103  	// Keep the extranonce1 and extranonce2_size from the reply
   104  	if sc.extranonce1, err = encoding.HexStringToBytes(reply[1]); err != nil {
   105  		log.Println("ERROR Invalid extrannonce1 from stratum")
   106  		sc.tcpclient.Close()
   107  		return
   108  	}
   109  
   110  	extranonce2Size, ok := reply[2].(float64)
   111  	if !ok {
   112  		log.Println("ERROR Invalid extranonce2_size from stratum", reply[2], "type", reflect.TypeOf(reply[2]))
   113  		sc.tcpclient.Close()
   114  		return
   115  	}
   116  	sc.extranonce2Size = uint(extranonce2Size)
   117  
   118  	log.Println("About to launch authorization goroutine")
   119  	// Authorize the miner
   120  	result, err = sc.tcpclient.Call("mining.authorize", []string{sc.User, ""})
   121  	if err != nil {
   122  		log.Println("Unable to authorize:", err)
   123  		return
   124  	}
   125  	log.Println("Authorization of", sc.User, ":", result)
   126  }
   127  
   128  // Start connects to the stratumserver and processes the notifications
   129  func (sc *StratumClient) Start() {
   130  	log.SetFlags(log.LstdFlags | log.Lshortfile)
   131  	log.Println("Starting StratumClient")
   132  	sc.mutex.Lock()
   133  	if sc.shouldstop {
   134  		return
   135  	}
   136  	sc.mutex.Unlock()
   137  	if err := sc.tg.Add(); err != nil {
   138  		build.Critical(err)
   139  	}
   140  	/*
   141  		else {
   142  			// we can get here if we stop the client before we finished starting it,
   143  			// so just return
   144  			return
   145  		}
   146  	*/
   147  	defer func() {
   148  		sc.tg.Done()
   149  		log.Println("Finished Starting StratumClient")
   150  	}()
   151  
   152  	select {
   153  	// if we've asked the thread to stop, don't proceed
   154  	case <-sc.tg.StopChan():
   155  		log.Println("tried starting stratumclient, but stop has already been called. exiting")
   156  		return
   157  	default:
   158  	}
   159  
   160  	sc.mutex.Lock()
   161  
   162  	sc.DeprecateOutstandingJobs()
   163  	sc.tcpclient = &TcpClient{}
   164  	sc.tcpinited = true
   165  	sc.subscribeToStratumDifficultyChanges()
   166  	sc.subscribeToStratumJobNotifications()
   167  
   168  	sc.mutex.Unlock()
   169  	errorCount := 0
   170  
   171  	// Connect to the stratum server
   172  	for {
   173  		select {
   174  		case <-sc.tg.StopChan():
   175  			return
   176  		default:
   177  		}
   178  		log.Println("Connecting to", sc.connectionstring)
   179  		err := sc.tcpclient.Dial(sc.connectionstring)
   180  		if err != nil {
   181  			//log.Println("TCP dialing failed")
   182  			log.Printf("ERROR Error in making tcp connection %d:%s", errorCount, err)
   183  			errorCount++
   184  			if errorCount >= 5 {
   185  				return
   186  			}
   187  			continue
   188  		} else {
   189  			errorCount = 0
   190  			break
   191  		}
   192  	}
   193  	// In case of an error, drop the current tcpclient and restart
   194  	sc.tcpclient.ErrorCallback = sc.RestartOnError
   195  	sc.SubscribeAndAuthorize()
   196  
   197  }
   198  
   199  func (sc *StratumClient) subscribeToStratumDifficultyChanges() {
   200  	sc.tcpclient.SetNotificationHandler("mining.set_difficulty", func(params []interface{}) {
   201  		if params == nil || len(params) < 1 {
   202  			log.Println("ERROR No difficulty parameter supplied by stratum server")
   203  			return
   204  		}
   205  		diff, ok := params[0].(float64)
   206  		//diff, err := strconv.ParseFloat(params[0].(string), 64)
   207  		if !ok {
   208  			log.Println("ERROR Invalid difficulty supplied by stratum server:", params[0])
   209  			return
   210  		}
   211  		if diff == 0 {
   212  			log.Println("ERROR Invalid difficulty supplied by stratum server:", diff)
   213  			return
   214  		}
   215  		log.Println("Stratum server changed difficulty to", diff)
   216  		sc.setDifficulty(diff)
   217  	})
   218  }
   219  
   220  // Stop shuts down the stratumclient and closes the tcp connection
   221  func (sc *StratumClient) Stop() {
   222  	log.Println("Stopping StratumClient")
   223  	if err := sc.tg.Stop(); err != nil {
   224  		log.Println("Error stopping StratumClient")
   225  	}
   226  	sc.mutex.Lock()
   227  	defer func() {
   228  		sc.mutex.Unlock()
   229  		log.Println("Finished Stopping StratumClient")
   230  	}()
   231  	sc.shouldstop = true
   232  	// we need to make sure we've actually Start()ed before can Close() the tcpclient
   233  	if sc.tcpinited {
   234  		sc.tcpinited = false
   235  		sc.DeprecateOutstandingJobs()
   236  		sc.tcpclient.Close()
   237  	}
   238  }
   239  
   240  // Connected returns whether or not the stratumclient has an open tcp
   241  // connection
   242  func (sc *StratumClient) Connected() bool {
   243  	sc.mutex.Lock()
   244  	defer sc.mutex.Unlock()
   245  	if sc.tcpinited {
   246  		return sc.tcpclient.Connected()
   247  	}
   248  	return false
   249  }
   250  
   251  func (sc *StratumClient) subscribeToStratumJobNotifications() {
   252  	sc.tcpclient.SetNotificationHandler("mining.notify", func(params []interface{}) {
   253  		log.Println("New job received from stratum server")
   254  		if params == nil || len(params) < 9 {
   255  			log.Println("ERROR Wrong number of parameters supplied by stratum server")
   256  			return
   257  		}
   258  
   259  		sj := stratumJob{}
   260  
   261  		sj.ExtraNonce2.Size = sc.extranonce2Size
   262  
   263  		var ok bool
   264  		var err error
   265  		if sj.JobID, ok = params[0].(string); !ok {
   266  			log.Println("ERROR Wrong job_id parameter supplied by stratum server")
   267  			return
   268  		}
   269  		if sj.PrevHash, err = encoding.HexStringToBytes(params[1]); err != nil {
   270  			log.Println(params[1])
   271  			log.Println(err)
   272  			log.Println("ERROR Wrong prevhash parameter supplied by stratum server")
   273  			return
   274  		}
   275  		if sj.Coinbase1, err = encoding.HexStringToBytes(params[2]); err != nil {
   276  			log.Println("ERROR Wrong coinb1 parameter supplied by stratum server")
   277  			return
   278  		}
   279  		if sj.Coinbase2, err = encoding.HexStringToBytes(params[3]); err != nil {
   280  			log.Println("ERROR Wrong coinb2 parameter supplied by stratum server")
   281  			return
   282  		}
   283  
   284  		//Convert the merklebranch parameter
   285  		merklebranch, ok := params[4].([]interface{})
   286  		if !ok {
   287  			log.Println("ERROR Wrong merkle_branch parameter supplied by stratum server")
   288  			return
   289  		}
   290  		sj.MerkleBranch = make([][]byte, len(merklebranch), len(merklebranch))
   291  		for i, branch := range merklebranch {
   292  			if sj.MerkleBranch[i], err = encoding.HexStringToBytes(branch); err != nil {
   293  				log.Println("ERROR Wrong merkle_branch parameter supplied by stratum server")
   294  				return
   295  			}
   296  		}
   297  
   298  		if sj.Version, ok = params[5].(string); !ok {
   299  			log.Println("ERROR Wrong version parameter supplied by stratum server")
   300  			return
   301  		}
   302  		if sj.NBits, ok = params[6].(string); !ok {
   303  			log.Println("ERROR Wrong nbits parameter supplied by stratum server")
   304  			return
   305  		}
   306  		if sj.NTime, err = encoding.HexStringToBytes(params[7]); err != nil {
   307  			log.Println("ERROR Wrong ntime parameter supplied by stratum server")
   308  			return
   309  		}
   310  		if sj.CleanJobs, ok = params[8].(bool); !ok {
   311  			log.Println("ERROR Wrong clean_jobs parameter supplied by stratum server")
   312  			return
   313  		}
   314  		sc.addNewStratumJob(sj)
   315  	})
   316  }
   317  
   318  func (sc *StratumClient) addNewStratumJob(sj stratumJob) {
   319  	sc.mutex.Lock()
   320  	defer sc.mutex.Unlock()
   321  	sc.currentJob = sj
   322  	if sj.CleanJobs {
   323  		sc.DeprecateOutstandingJobs()
   324  	}
   325  	sc.AddJobToDeprecate(sj.JobID)
   326  }
   327  
   328  // IntToTarget converts a big.Int to a Target.
   329  func intToTarget(i *big.Int) (t types.Target, err error) {
   330  	// Check for negatives.
   331  	if i.Sign() < 0 {
   332  		err = errors.New("Negative target")
   333  		return
   334  	}
   335  	// In the event of overflow, return the maximum.
   336  	if i.BitLen() > 256 {
   337  		err = errors.New("Target is too high")
   338  		return
   339  	}
   340  	b := i.Bytes()
   341  	offset := len(t[:]) - len(b)
   342  	copy(t[offset:], b)
   343  	return
   344  }
   345  
   346  func difficultyToTarget(difficulty float64) (target types.Target, err error) {
   347  	diffAsBig := big.NewFloat(difficulty)
   348  
   349  	diffOneString := "0x00000000ffff0000000000000000000000000000000000000000000000000000"
   350  	targetOneAsBigInt := &big.Int{}
   351  	targetOneAsBigInt.SetString(diffOneString, 0)
   352  
   353  	targetAsBigFloat := &big.Float{}
   354  	targetAsBigFloat.SetInt(targetOneAsBigInt)
   355  	targetAsBigFloat.Quo(targetAsBigFloat, diffAsBig)
   356  	targetAsBigInt, _ := targetAsBigFloat.Int(nil)
   357  	target, err = intToTarget(targetAsBigInt)
   358  	return
   359  }
   360  
   361  func (sc *StratumClient) setDifficulty(difficulty float64) {
   362  	target, err := difficultyToTarget(difficulty)
   363  	if err != nil {
   364  		log.Println("ERROR Error setting difficulty to ", difficulty)
   365  	}
   366  	sc.mutex.Lock()
   367  	defer sc.mutex.Unlock()
   368  	sc.target = target
   369  	//fmt.Printf("difficulty set to: %d\n", difficulty)
   370  }
   371  
   372  //GetHeaderForWork fetches new work from the SIA daemon
   373  func (sc *StratumClient) GetHeaderForWork() (target, header []byte, deprecationChannel chan bool, job interface{}, err error) {
   374  	sc.mutex.Lock()
   375  	defer sc.mutex.Unlock()
   376  
   377  	job = sc.currentJob
   378  	if sc.currentJob.JobID == "" {
   379  		err = errors.New("No job received from stratum server yet")
   380  		return
   381  	}
   382  
   383  	deprecationChannel = sc.GetDeprecationChannel(sc.currentJob.JobID)
   384  
   385  	target = sc.target[:]
   386  
   387  	//Create the arbitrary transaction
   388  	en2 := sc.currentJob.ExtraNonce2.Bytes()
   389  	err = sc.currentJob.ExtraNonce2.Increment()
   390  
   391  	arbtx := []byte{0}
   392  	arbtx = append(arbtx, sc.currentJob.Coinbase1...)
   393  	arbtx = append(arbtx, sc.extranonce1...)
   394  	arbtx = append(arbtx, en2...)
   395  	arbtx = append(arbtx, sc.currentJob.Coinbase2...)
   396  	arbtxHash := crypto.HashBytes(arbtx)
   397  
   398  	//Construct the merkleroot from the arbitrary transaction and the merklebranches
   399  	merkleRoot := arbtxHash
   400  	for _, h := range sc.currentJob.MerkleBranch {
   401  		m := append([]byte{1}[:], h...)
   402  		m = append(m, merkleRoot[:]...)
   403  		merkleRoot = crypto.HashBytes(m)
   404  	}
   405  
   406  	//Construct the header
   407  	header = make([]byte, 0, 80)
   408  	header = append(header, sc.currentJob.PrevHash...)
   409  	header = append(header, []byte{0, 0, 0, 0, 0, 0, 0, 0}[:]...) //empty nonce
   410  	header = append(header, sc.currentJob.NTime...)
   411  	header = append(header, merkleRoot[:]...)
   412  
   413  	return
   414  }
   415  
   416  //SubmitHeader reports a solution to the stratum server
   417  func (sc *StratumClient) SubmitHeader(header []byte, job interface{}) (err error) {
   418  	sj, _ := job.(stratumJob)
   419  	nonce := encoding.BytesToHexString(header[32:40])
   420  	encodedExtraNonce2 := encoding.BytesToHexString(sj.ExtraNonce2.Bytes())
   421  	nTime := encoding.BytesToHexString(sj.NTime)
   422  	sc.mutex.Lock()
   423  	c := sc.tcpclient
   424  	sc.mutex.Unlock()
   425  	stratumUser := sc.User
   426  	_, err = c.Call("mining.submit", []string{stratumUser, sj.JobID, encodedExtraNonce2, nTime, nonce})
   427  	if err != nil {
   428  		return
   429  	}
   430  	return
   431  }