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

     1  package pool
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"encoding/binary"
     7  	"encoding/hex"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"io"
    12  	// "math/big"
    13  	"net"
    14  	"strconv"
    15  	"strings"
    16  	"time"
    17  
    18  	"github.com/sasha-s/go-deadlock"
    19  
    20  	"gitlab.com/SiaPrime/SiaPrime/encoding"
    21  	"gitlab.com/SiaPrime/SiaPrime/modules"
    22  	"gitlab.com/SiaPrime/SiaPrime/persist"
    23  	"gitlab.com/SiaPrime/SiaPrime/types"
    24  )
    25  
    26  const (
    27  	extraNonce2Size = 4
    28  )
    29  
    30  // Handler represents the status (open/closed) of each connection
    31  type Handler struct {
    32  	mu     deadlock.RWMutex
    33  	conn   net.Conn
    34  	closed chan bool
    35  	notify chan bool
    36  	p      *Pool
    37  	log    *persist.Logger
    38  	s      *Session
    39  }
    40  
    41  const (
    42  	blockTimeout = 5 * time.Second
    43  	// This should represent the max number of pending notifications (new blocks found) within blockTimeout seconds
    44  	// better to have too many, than not enough (causes a deadlock otherwise)
    45  	numPendingNotifies = 20
    46  )
    47  
    48  func (h *Handler) parseRequest() (*types.StratumRequest, error) {
    49  	var m types.StratumRequest
    50  	// fmt.Printf("%s: Setting timeout for %v\n", h.s.printID(), time.Now().Add(blockTimeout))
    51  	h.conn.SetReadDeadline(time.Now().Add(blockTimeout))
    52  	//dec := json.NewDecoder(h.conn)
    53  	buf := bufio.NewReader(h.conn)
    54  	select {
    55  	case <-h.p.tg.StopChan():
    56  		return nil, errors.New("Stopping handler")
    57  	case <-h.notify:
    58  		m.Method = "mining.notify"
    59  	default:
    60  		// try reading from the connection
    61  		//err = dec.Decode(&m)
    62  		str, err := buf.ReadString('\n')
    63  		// if we timeout
    64  		if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
    65  			// h.log.Printf("%s: Harmless timeout occurred\n", h.s.printID())
    66  			//h.conn.SetReadDeadline(time.Time{})
    67  			// check last job time and if over 25 seconds, send a new job.
    68  			// if time.Now().Sub(h.s.lastJobTimestamp) > (time.Second * 25) {
    69  			// 	m.Method = "mining.notify"
    70  			// 	break
    71  			// }
    72  			if h.s.DetectDisconnected() {
    73  				h.log.Println("Non-responsive disconnect detected!")
    74  				return nil, errors.New("Non-responsive disconnect detected")
    75  			}
    76  
    77  			// ratio := h.s.CurrentDifficulty() / h.s.HighestDifficulty()
    78  			// h.log.Printf("Non-responsive disconnect ratio: %f\n", ratio)
    79  
    80  			if h.s.checkDiffOnNewShare() {
    81  				err = h.sendSetDifficulty(h.s.CurrentDifficulty())
    82  				if err != nil {
    83  					h.log.Println("Error sending SetDifficulty")
    84  					return nil, err
    85  				}
    86  				err = h.sendStratumNotify(true)
    87  				if err != nil {
    88  					h.log.Println("Error sending stratum notify")
    89  					return nil, err
    90  				}
    91  			}
    92  
    93  			return nil, nil
    94  			// if we don't timeout but have some other error
    95  		} else if err != nil {
    96  			if err == io.EOF {
    97  				//fmt.Println("End connection")
    98  				h.log.Println("End connection")
    99  			} else {
   100  				h.log.Println("Unusual error")
   101  				h.log.Println(err)
   102  			}
   103  			return nil, err
   104  		} else {
   105  			// NOTE: we were getting weird cases where the buffer would read a full
   106  			// string, then read part of the string again (like the last few chars).
   107  			// Somehow it seemed like the socket data wasn't getting purged correctly
   108  			// on reading.
   109  			// Not seeing it lately, but that's why we have this debugging code,
   110  			// and that's why we don't just read straight into the JSON decoder.
   111  			// Reading the data into a string buffer lets us debug more easily.
   112  
   113  			// If we encounter this issue again, we may want to drop down into
   114  			// lower-level socket manipulation to try to troubleshoot it.
   115  			// h.log.Println(str)
   116  			dec := json.NewDecoder(strings.NewReader(str))
   117  			err = dec.Decode(&m)
   118  			if err != nil {
   119  				h.log.Println("Decoding error")
   120  				h.log.Println(err)
   121  				h.log.Println(str)
   122  				//return nil, err
   123  				// don't disconnect here, just treat it like a harmless timeout
   124  				return nil, nil
   125  			}
   126  		}
   127  	}
   128  	// h.log.Printf("%s: Clearing timeout\n", h.s.printID())
   129  	return &m, nil
   130  }
   131  
   132  func (h *Handler) handleRequest(m *types.StratumRequest) error {
   133  	h.s.SetHeartbeat()
   134  	var err error
   135  	switch m.Method {
   136  	case "mining.subscribe":
   137  		return h.handleStratumSubscribe(m)
   138  	case "mining.authorize":
   139  		err = h.handleStratumAuthorize(m)
   140  		if err != nil {
   141  			h.log.Printf("Failed to authorize client\n")
   142  			h.log.Println(err)
   143  			return err
   144  		}
   145  		return h.sendStratumNotify(true)
   146  	case "mining.extranonce.subscribe":
   147  		return h.handleStratumNonceSubscribe(m)
   148  	case "mining.submit":
   149  		return h.handleStratumSubmit(m)
   150  	case "mining.notify":
   151  		h.log.Printf("mining.notify:New block to mine on\n")
   152  		return h.sendStratumNotify(true)
   153  	default:
   154  		h.log.Debugln("Unknown stratum method: ", m.Method)
   155  	}
   156  	return nil
   157  }
   158  
   159  // Listen listens on a connection for incoming data and acts on it
   160  func (h *Handler) Listen() {
   161  	defer func() {
   162  		h.closed <- true // send to dispatcher, that connection is closed
   163  		h.conn.Close()
   164  		if h.s != nil && h.s.CurrentWorker != nil {
   165  			// delete a worker record when a session disconnections
   166  			h.s.CurrentWorker.deleteWorkerRecord()
   167  			// when we shut down the pool we get an error here because the log
   168  			// is already closed... TODO
   169  			h.s.log.Printf("Closed worker: %d\n", h.s.CurrentWorker.wr.workerID)
   170  		}
   171  	}()
   172  	err := h.p.tg.Add()
   173  	if err != nil {
   174  		// If this goroutine is not run before shutdown starts, this
   175  		// codeblock is reachable.
   176  		return
   177  	}
   178  	defer h.p.tg.Done()
   179  
   180  	h.log.Println("New connection from " + h.conn.RemoteAddr().String())
   181  	h.mu.Lock()
   182  	h.s, _ = newSession(h.p, h.conn.RemoteAddr().String())
   183  	h.mu.Unlock()
   184  	h.log.Println("New session: " + sPrintID(h.s.SessionID))
   185  	for {
   186  		m, err := h.parseRequest()
   187  		h.conn.SetReadDeadline(time.Time{})
   188  		// if we timed out
   189  		if m == nil && err == nil {
   190  			continue
   191  			// else if we got a request
   192  		} else if m != nil {
   193  			err = h.handleRequest(m)
   194  			if err != nil {
   195  				h.log.Println(err)
   196  				return
   197  			}
   198  			// else if we got an error
   199  		} else if err != nil {
   200  			h.log.Println(err)
   201  			return
   202  		}
   203  	}
   204  }
   205  
   206  func (h *Handler) sendResponse(r types.StratumResponse) error {
   207  	b, err := json.Marshal(r)
   208  	//fmt.Printf("SERVER: %s\n", b)
   209  	if err != nil {
   210  		h.log.Debugln("json marshal failed for id: ", r.ID, err)
   211  		return err
   212  	}
   213  	b = append(b, '\n')
   214  	_, err = h.conn.Write(b)
   215  	if err != nil {
   216  		h.log.Debugln("connection write failed for id: ", r.ID, err)
   217  		return err
   218  	}
   219  	return nil
   220  }
   221  
   222  func (h *Handler) sendRequest(r types.StratumRequest) error {
   223  	b, err := json.Marshal(r)
   224  	if err != nil {
   225  		h.log.Debugln("json marshal failed for id: ", r.ID, err)
   226  		return err
   227  	}
   228  	b = append(b, '\n')
   229  	_, err = h.conn.Write(b)
   230  	h.log.Debugln("sending request: ", string(b))
   231  	if err != nil {
   232  		h.log.Debugln("connection write failed for id: ", r.ID, err)
   233  		return err
   234  	}
   235  	return nil
   236  }
   237  
   238  // handleStratumSubscribe message is the first message received and allows the pool to tell the miner
   239  // the difficulty as well as notify, extranonce1 and extranonce2
   240  //
   241  // TODO: Pull the appropriate data from either in memory or persistent store as required
   242  func (h *Handler) handleStratumSubscribe(m *types.StratumRequest) error {
   243  	if len(m.Params) > 0 {
   244  		h.log.Printf("Client subscribe name:%s", m.Params[0].(string))
   245  		h.s.SetClientVersion(m.Params[0].(string))
   246  	}
   247  
   248  	if len(m.Params) > 0 && m.Params[0].(string) == "sgminer/4.4.2" {
   249  		h.s.SetHighestDifficulty(11500)
   250  		h.s.SetCurrentDifficulty(11500)
   251  		h.s.SetDisableVarDiff(true)
   252  	}
   253  	if len(m.Params) > 0 && m.Params[0].(string) == "cgminer/4.9.0" {
   254  		h.s.SetHighestDifficulty(1024)
   255  		h.s.SetCurrentDifficulty(1024)
   256  		h.s.SetDisableVarDiff(true)
   257  	}
   258  	if len(m.Params) > 0 && m.Params[0].(string) == "cgminer/4.10.0" {
   259  		h.s.SetHighestDifficulty(700)
   260  		h.s.SetCurrentDifficulty(700)
   261  		h.s.SetDisableVarDiff(true)
   262  	}
   263  	if len(m.Params) > 0 && m.Params[0].(string) == "gominer" {
   264  		h.s.SetHighestDifficulty(0.03)
   265  		h.s.SetCurrentDifficulty(0.03)
   266  		h.s.SetDisableVarDiff(true)
   267  	}
   268  	if len(m.Params) > 0 && m.Params[0].(string) == "NiceHash/1.0.0" {
   269  		r := types.StratumResponse{ID: m.ID}
   270  		r.Result = false
   271  		r.Error = interfaceify([]string{"NiceHash client is not supported"})
   272  		return h.sendResponse(r)
   273  	}
   274  
   275  	r := types.StratumResponse{ID: m.ID}
   276  	r.Method = m.Method
   277  	/*
   278  		if !h.s.Authorized() {
   279  			r.Result = false
   280  			r.Error = interfaceify([]string{"Session not authorized - authorize before subscribe"})
   281  			return h.sendResponse(r)
   282  		}
   283  	*/
   284  
   285  	//	diff := "b4b6693b72a50c7116db18d6497cac52"
   286  	t, _ := h.p.persist.Target.Difficulty().Uint64()
   287  	h.log.Debugf("Block Difficulty: %x\n", t)
   288  	tb := make([]byte, 8)
   289  	binary.LittleEndian.PutUint64(tb, t)
   290  	diff := hex.EncodeToString(tb)
   291  	//notify := "ae6812eb4cd7735a302a8a9dd95cf71f"
   292  	notify := h.s.printID()
   293  	extranonce1 := h.s.printNonce()
   294  	extranonce2 := extraNonce2Size
   295  	raw := fmt.Sprintf(`[ [ ["mining.set_difficulty", "%s"], ["mining.notify", "%s"]], "%s", %d]`, diff, notify, extranonce1, extranonce2)
   296  	r.Result = json.RawMessage(raw)
   297  	r.Error = nil
   298  	return h.sendResponse(r)
   299  }
   300  
   301  // this is thread-safe, when we're looking for and possibly creating a client,
   302  // we need to make sure the action is atomic
   303  func (h *Handler) setupClient(client, worker string) (*Client, error) {
   304  	var err error
   305  	h.p.clientSetupMutex.Lock()
   306  	defer h.p.clientSetupMutex.Unlock()
   307  	c, err := h.p.FindClientDB(client)
   308  	if err != ErrNoUsernameInDatabase {
   309  		return c, err
   310  	}
   311  	if c == nil {
   312  		//fmt.Printf("Unable to find client in db: %s\n", client)
   313  		c, err = newClient(h.p, client)
   314  		if err != nil {
   315  			//fmt.Println("Failed to create a new Client")
   316  			h.p.log.Printf("Failed to create a new Client: %s\n", err)
   317  			return nil, err
   318  		}
   319  		err = h.p.AddClientDB(c)
   320  		if err != nil {
   321  			h.p.log.Printf("Failed to add client to DB: %s\n", err)
   322  			return nil, err
   323  		}
   324  	} else {
   325  		//fmt.Printf("Found client: %s\n", client)
   326  	}
   327  	if h.p.Client(client) == nil {
   328  		h.p.log.Printf("Adding client in memory: %s\n", client)
   329  		h.p.AddClient(c)
   330  	}
   331  	return c, nil
   332  }
   333  
   334  func (h *Handler) setupWorker(c *Client, workerName string) (*Worker, error) {
   335  	w, err := newWorker(c, workerName, h.s)
   336  	if err != nil {
   337  		c.log.Printf("Failed to add worker: %s\n", err)
   338  		return nil, err
   339  	}
   340  
   341  	err = c.addWorkerDB(w)
   342  	if err != nil {
   343  		c.log.Printf("Failed to add worker: %s\n", err)
   344  		return nil, err
   345  	}
   346  	h.s.log = w.log
   347  	c.log.Printf("Adding new worker: %s, %d\n", workerName, w.wr.workerID)
   348  	w.log.Printf("Adding new worker: %s, %d\n", workerName, w.wr.workerID)
   349  	h.log.Debugln("client = " + c.Name() + ", worker = " + workerName)
   350  	return w, nil
   351  }
   352  
   353  // handleStratumAuthorize allows the pool to tie the miner connection to a particular user
   354  func (h *Handler) handleStratumAuthorize(m *types.StratumRequest) error {
   355  	var err error
   356  
   357  	r := types.StratumResponse{ID: m.ID}
   358  
   359  	r.Method = "mining.authorize"
   360  	r.Result = true
   361  	r.Error = nil
   362  	clientName := m.Params[0].(string)
   363  	workerName := ""
   364  	if strings.Contains(clientName, ".") {
   365  		s := strings.SplitN(clientName, ".", -1)
   366  		clientName = s[0]
   367  		workerName = s[1]
   368  	}
   369  
   370  	// load wallet and check validity
   371  	var walletTester types.UnlockHash
   372  	err = walletTester.LoadString(clientName)
   373  	if err != nil {
   374  		r.Result = false
   375  		r.Error = interfaceify([]string{"Client Name must be valid wallet address"})
   376  		h.log.Println("Client Name must be valid wallet address. Client name is: " + clientName)
   377  		err = errors.New("Client name must be a valid wallet address")
   378  		h.sendResponse(r)
   379  		return err
   380  	}
   381  
   382  	c, err := h.setupClient(clientName, workerName)
   383  	if err != nil {
   384  		r.Result = false
   385  		r.Error = interfaceify([]string{err.Error()})
   386  		h.sendResponse(r)
   387  		return err
   388  	}
   389  	w, err := h.setupWorker(c, workerName)
   390  	if err != nil {
   391  		r.Result = false
   392  		r.Error = interfaceify([]string{err.Error()})
   393  		h.sendResponse(r)
   394  		return err
   395  	}
   396  
   397  	// if everything is fine, setup the client and send a response and difficulty
   398  	h.s.addClient(c)
   399  	h.s.addWorker(w)
   400  	h.s.addShift(h.p.newShift(h.s.CurrentWorker))
   401  	h.s.SetAuthorized(true)
   402  	err = h.sendResponse(r)
   403  	if err != nil {
   404  		return err
   405  	}
   406  	err = h.sendSetDifficulty(h.s.CurrentDifficulty())
   407  	if err != nil {
   408  		return err
   409  	}
   410  	return nil
   411  }
   412  
   413  // handleStratumNonceSubscribe tells the pool that this client can handle the extranonce info
   414  // TODO: Not sure we have to anything if all our clients support this.
   415  func (h *Handler) handleStratumNonceSubscribe(m *types.StratumRequest) error {
   416  	h.p.log.Debugln("ID = "+strconv.FormatUint(m.ID, 10)+", Method = "+m.Method+", params = ", m.Params)
   417  
   418  	// not sure why 3 is right, but ccminer expects it to be 3
   419  	r := types.StratumResponse{ID: 3}
   420  	r.Result = true
   421  	r.Error = nil
   422  
   423  	return h.sendResponse(r)
   424  }
   425  
   426  // request is sent as [name, jobid, extranonce2, nTime, nonce]
   427  func (h *Handler) handleStratumSubmit(m *types.StratumRequest) error {
   428  	// fmt.Printf("%s: %s Handle submit\n", time.Now(), h.s.printID())
   429  	r := types.StratumResponse{ID: m.ID}
   430  	r.Method = "mining.submit"
   431  	r.Result = true
   432  	r.Error = nil
   433  	/*
   434  		err := json.Unmarshal(m.Params, &p)
   435  		if err != nil {
   436  			h.log.Printf("Unable to parse mining.submit params: %v\n", err)
   437  			r.Result = false //json.RawMessage(`false`)
   438  			r.Error = interfaceify([]string{"20","Parse Error"}) //json.RawMessage(`["20","Parse Error"]`)
   439  		}
   440  	*/
   441  	// name := m.Params[0].(string)
   442  	var jobID uint64
   443  	fmt.Sscanf(m.Params[1].(string), "%x", &jobID)
   444  	extraNonce2 := m.Params[2].(string)
   445  	nTime := m.Params[3].(string)
   446  	nonce := m.Params[4].(string)
   447  	// h.s.log.Printf("Submit jobid:%d nonce: %s, extraNonce2: %s", jobID, nonce, extraNonce2)
   448  
   449  	needNewJob := false
   450  	defer func() {
   451  		if needNewJob == true {
   452  			h.sendStratumNotify(true)
   453  		}
   454  	}()
   455  
   456  	if h.s.CurrentWorker == nil {
   457  		// worker failed to authorize
   458  		h.log.Printf("Worker failed to authorize - dropping\n")
   459  		return errors.New("Worker failed to authorize - dropping")
   460  	}
   461  
   462  	h.s.SetLastShareTimestamp(time.Now())
   463  	sessionPoolDifficulty := h.s.CurrentDifficulty()
   464  	if h.s.checkDiffOnNewShare() {
   465  		h.sendSetDifficulty(h.s.CurrentDifficulty())
   466  		needNewJob = true
   467  	}
   468  
   469  	// h.log.Debugln("name = " + name + ", jobID = " + fmt.Sprintf("%X", jobID) + ", extraNonce2 = " + extraNonce2 + ", nTime = " + nTime + ", nonce = " + nonce)
   470  	if h.s.log != nil {
   471  		// h.s.log.Debugln("name = " + name + ", jobID = " + fmt.Sprintf("%X", jobID) + ", extraNonce2 = " + extraNonce2 + ", nTime = " + nTime + ", nonce = " + nonce)
   472  	} else {
   473  		h.log.Debugln("session log not ready")
   474  	}
   475  
   476  	var b types.Block
   477  	j, err := h.s.getJob(jobID, nonce)
   478  	if err != nil {
   479  		r.Result = false
   480  		r.Error = interfaceify([]string{"23", err.Error()}) //json.RawMessage(`["21","Stale - old/unknown job"]`)
   481  		h.s.CurrentWorker.IncrementInvalidShares()
   482  		return h.sendResponse(r)
   483  	}
   484  	if j != nil {
   485  		encoding.Unmarshal(j.MarshalledBlock, &b)
   486  	}
   487  
   488  	if len(b.MinerPayouts) == 0 {
   489  		r.Result = false
   490  		r.Error = interfaceify([]string{"22", "Stale - old/unknown job"}) //json.RawMessage(`["21","Stale - old/unknown job"]`)
   491  		h.s.CurrentWorker.log.Printf("Stale Share rejected - old/unknown job\n")
   492  		h.s.CurrentWorker.IncrementInvalidShares()
   493  		return h.sendResponse(r)
   494  	}
   495  
   496  	bhNonce, err := hex.DecodeString(nonce)
   497  	for i := range b.Nonce { // there has to be a better way to do this in golang
   498  		b.Nonce[i] = bhNonce[i]
   499  	}
   500  	tb, _ := hex.DecodeString(nTime)
   501  	b.Timestamp = types.Timestamp(encoding.DecUint64(tb))
   502  
   503  	cointxn := h.p.coinB1()
   504  	ex1, _ := hex.DecodeString(h.s.printNonce())
   505  	ex2, _ := hex.DecodeString(extraNonce2)
   506  	cointxn.ArbitraryData[0] = append(cointxn.ArbitraryData[0], ex1...)
   507  	cointxn.ArbitraryData[0] = append(cointxn.ArbitraryData[0], ex2...)
   508  
   509  	b.Transactions = append(b.Transactions, []types.Transaction{cointxn}...)
   510  	blockHash := b.ID()
   511  	// bh := new(big.Int).SetBytes(blockHash[:])
   512  
   513  	sessionPoolTarget, _ := difficultyToTarget(sessionPoolDifficulty)
   514  
   515  	// h.s.CurrentWorker.log.Printf("session diff: %f, block version diff: %s",
   516  	// 	sessionPoolDifficulty, printWithSuffix(sessionPoolTarget.Difficulty()))
   517  
   518  	// need to checkout the block hashrate reach pool target or not
   519  	// h.s.CurrentWorker.log.Printf("Submit target: %064x\n", bh)
   520  	// h.s.CurrentWorker.log.Printf("Session target:   %064x\n", sessionPoolTarget.Int())
   521  	if bytes.Compare(sessionPoolTarget[:], blockHash[:]) < 0 {
   522  		r.Result = false
   523  		r.Error = interfaceify([]string{"22", "Submit nonce not reach pool diff target"}) //json.RawMessage(`["21","Stale - old/unknown job"]`)
   524  		h.s.CurrentWorker.log.Printf("Submit nonce not reach pool diff target\n")
   525  		h.s.CurrentWorker.IncrementInvalidShares()
   526  		return h.sendResponse(r)
   527  	}
   528  
   529  	t := h.p.persist.GetTarget()
   530  	// h.s.CurrentWorker.log.Printf("Submit block hash is   %064x\n", bh)
   531  	// h.s.CurrentWorker.log.Printf("Chain block target is  %064x\n", t.Int())
   532  	// h.s.CurrentWorker.log.Printf("Difficulty %s/%s\n",
   533  	// 		printWithSuffix(types.IntToTarget(bh).Difficulty()), printWithSuffix(t.Difficulty()))
   534  	if bytes.Compare(t[:], blockHash[:]) < 0 {
   535  		// h.s.CurrentWorker.log.Printf("Block hash is greater than block target\n")
   536  		h.s.CurrentWorker.log.Printf("Share Accepted\n")
   537  		h.s.CurrentWorker.IncrementShares(h.s.CurrentDifficulty(), currencyToAmount(b.MinerPayouts[0].Value))
   538  		h.s.CurrentWorker.SetLastShareTime(time.Now())
   539  		return h.sendResponse(r)
   540  	}
   541  	err = h.p.managedSubmitBlock(b)
   542  	if err != nil && err != modules.ErrBlockUnsolved {
   543  		h.log.Printf("Failed to SubmitBlock(): %v\n", err)
   544  		h.log.Printf(sPrintBlock(b))
   545  		panic(fmt.Sprintf("Failed to SubmitBlock(): %v\n", err))
   546  		/*r.Result = false //json.RawMessage(`false`)
   547  		r.Error = interfaceify([]string{"20", "Stale share"})
   548  		h.s.CurrentWorker.IncrementInvalidShares()
   549  		return h.sendResponse(r)*/
   550  	}
   551  
   552  	h.s.CurrentWorker.log.Printf("Share Accepted\n")
   553  	h.s.CurrentWorker.IncrementShares(h.s.CurrentDifficulty(), currencyToAmount(b.MinerPayouts[0].Value))
   554  	h.s.CurrentWorker.SetLastShareTime(time.Now())
   555  
   556  	// TODO: why not err == nil ?
   557  	if err != modules.ErrBlockUnsolved {
   558  		h.s.CurrentWorker.Parent().log.Printf("Yay!!! Solved a block!!\n")
   559  		// h.s.CurrentWorker.log.Printf("Yay!!! Solved a block!!\n")
   560  		h.s.clearJobs()
   561  		err = h.s.CurrentWorker.addFoundBlock(&b)
   562  		if err != nil {
   563  			h.s.CurrentWorker.log.Printf("Failed to update block in database: %s\n", err)
   564  		}
   565  		h.p.shiftChan <- true
   566  	}
   567  	return h.sendResponse(r)
   568  }
   569  
   570  func (h *Handler) sendSetDifficulty(d float64) error {
   571  	var r types.StratumRequest
   572  
   573  	r.Method = "mining.set_difficulty"
   574  	// assuming this ID is the response to the original subscribe which appears to be a 1
   575  	r.ID = 0
   576  	params := make([]interface{}, 1)
   577  	r.Params = params
   578  	r.Params[0] = d
   579  	return h.sendRequest(r)
   580  }
   581  
   582  func (h *Handler) sendStratumNotify(cleanJobs bool) error {
   583  	var r types.StratumRequest
   584  	r.Method = "mining.notify"
   585  	r.ID = 0 // gominer requires notify to use an id of 0
   586  	job, _ := newJob(h.p)
   587  	h.s.addJob(job)
   588  	jobid := job.printID()
   589  	var b types.Block
   590  	h.p.persist.mu.Lock()
   591  	// make a copy of the block and hold it until the solution is submitted
   592  	job.MarshalledBlock = encoding.Marshal(&h.p.sourceBlock)
   593  	h.p.persist.mu.Unlock()
   594  	encoding.Unmarshal(job.MarshalledBlock, &b)
   595  	job.MerkleRoot = b.MerkleRoot()
   596  	mbj := b.MerkleBranches()
   597  	h.log.Debugf("merkleBranch: %s\n", mbj)
   598  
   599  	version := ""
   600  	nbits := fmt.Sprintf("%08x", BigToCompact(h.p.persist.Target.Int()))
   601  
   602  	var buf bytes.Buffer
   603  	encoding.WriteUint64(&buf, uint64(b.Timestamp))
   604  	ntime := hex.EncodeToString(buf.Bytes())
   605  
   606  	params := make([]interface{}, 9)
   607  	r.Params = params
   608  	r.Params[0] = jobid
   609  	r.Params[1] = b.ParentID.String()
   610  	r.Params[2] = h.p.coinB1Txn()
   611  	r.Params[3] = h.p.coinB2()
   612  	r.Params[4] = mbj
   613  	r.Params[5] = version
   614  	r.Params[6] = nbits
   615  	r.Params[7] = ntime
   616  	r.Params[8] = cleanJobs
   617  	//h.log.Debugf("send.notify: %s\n", raw)
   618  	h.log.Debugf("send.notify: %s\n", r.Params)
   619  	return h.sendRequest(r)
   620  }