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