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 }