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 }