From d908527792228fada4961d154915e42fdb2d514e Mon Sep 17 00:00:00 2001 From: cryptonote-social Date: Thu, 24 Dec 2020 16:49:37 -0800 Subject: [PATCH] initial client work for chat support --- capi/capi.go | 24 +++++++- capi/niceapi.h | 37 +++++++++++- capi/test.c | 61 ++++++++++++++++--- csminer.go | 2 +- miner.go | 16 ++++- minerlib/chat/chat.go | 93 ++++++++++++++++++++++++++++ minerlib/minerlib.go | 70 ++++++++++++++++++--- stratum/client/client.go | 127 ++++++++++++++++++++++++++------------- 8 files changed, 364 insertions(+), 66 deletions(-) create mode 100644 minerlib/chat/chat.go diff --git a/capi/capi.go b/capi/capi.go index 1e272df..d2f4936 100644 --- a/capi/capi.go +++ b/capi/capi.go @@ -4,6 +4,7 @@ import "C" import ( "github.com/cryptonote-social/csminer/minerlib" + "github.com/cryptonote-social/csminer/minerlib/chat" ) //export PoolLogin @@ -46,12 +47,31 @@ func GetMinerState() ( secondsOld int, lifetimeHashes int64, paid, owed, accumulated float64, - timeToReward *C.char) { + timeToReward *C.char, + chatsAvailable bool) { resp := minerlib.GetMiningState() return resp.MiningActivity, resp.Threads, resp.RecentHashrate, C.CString(resp.PoolUsername), resp.SecondsOld, resp.LifetimeHashes, - resp.Paid, resp.Owed, resp.Accumulated, C.CString(resp.TimeToReward) + resp.Paid, resp.Owed, resp.Accumulated, C.CString(resp.TimeToReward), + resp.ChatsAvailable +} + +//export NextChat +func NextChat() ( + username *C.char, + message *C.char, + timestamp int64) { + nc := chat.NextChatReceived() + if nc == nil { + return C.CString(""), C.CString(""), 0 + } + return C.CString(nc.Username), C.CString(nc.Message), nc.Timestamp +} + +//export SendChat +func SendChat(message *C.char) { + chat.SendChat(C.GoString(message)) } //export IncreaseThreads diff --git a/capi/niceapi.h b/capi/niceapi.h index 4276969..c7a2f3d 100644 --- a/capi/niceapi.h +++ b/capi/niceapi.h @@ -3,6 +3,7 @@ #include #include #include +#include #include typedef struct pool_login_args { @@ -114,6 +115,10 @@ typedef struct get_miner_state_response { // // MINING_ACTIVE_USER_OVERRIDE = 2 // indicates miner is actively mining, and is in "user forced active mining override" state. + // + // MINING_ACTIVE_CHATS_TO_SEND = 3 + // indicates miner is actively mining to generate a share so that a chat message can be delivered. + int mining_activity; int threads; // number of threads actively mining @@ -149,6 +154,8 @@ typedef struct get_miner_state_response { const char* time_to_reward; // An estimate of the time to next reward in a pretty-printable // format, e.g. "3.5 days". This is just an estimate based on pool // hashrate and other dynamic factors + + bool chats_available; // whether there are chat messages available to display (see next_chat) } get_miner_state_response; get_miner_state_response get_miner_state() { @@ -158,16 +165,42 @@ get_miner_state_response get_miner_state() { response.threads = (int)r.r1; response.recent_hashrate = (float)r.r2; response.username = r.r3; - response.time_to_reward = r.r9; response.seconds_old = (int)r.r4; response.lifetime_hashes = (long)r.r5; response.paid = (float)r.r6; response.owed = (float)r.r7; response.accumulated = (float)r.r8; - + response.time_to_reward = r.r9; + response.chats_available = (bool)r.r10; return response; } +typedef struct next_chat_response { + // NOTE: you must free() each const char* + const char* username; // username of the user who sent the chat (ascii) + const char* message; // the chat message (unicode) + int64_t timestamp; // unix timestamp of when the chat was received by chat server +} next_chat_response; + +// Return the next available chat message. If there are no chat messages left to return, +// the chat response will have empty username/message +next_chat_response next_chat() { + struct NextChat_return r = NextChat(); + next_chat_response response; + response.username = r.r0; + response.message = r.r1; + response.timestamp = (int64_t)r.r2; + return response; +} + +// Queue a chat message for sending. Returns a code indicating if successful (0) or not. Right now +// only success is returned. Message might not be sent immediately, e.g. miner may wait to send it +// with the next mined share. +int send_chat(char *message) { + SendChat(message); + return 0; +} + // Increase the number of threads by 1. This may fail. get_miner_state will // always report the true number of current threads. void increase_threads() { diff --git a/capi/test.c b/capi/test.c index 0b8c024..51afe5a 100644 --- a/capi/test.c +++ b/capi/test.c @@ -30,13 +30,30 @@ int main(int argc, char* argv[]) { report_lock_screen_state(true); // pretend screen is locked so we will mine pool_login_args pl_args; - pl_args.agent = "Super Power Ultimate Miner (S.P.U.M.) v0.6.9"; + pl_args.agent = "csminer / minerlib test script"; pl_args.rigid = NULL; pl_args.wallet = NULL; pl_args.config = NULL; + get_miner_state_response ms_resp; + // Login loop. Alternate between 2 accounts every minute to make sure account switching works. while (true) { + printf("Entering get_miner_state polling loop, 30 polls with 1 second sleep inbetween\n"); + for (int i = 0; i < 30; ++i) { + ms_resp = get_miner_state(); + printf("Hashrate was: %f\n", ms_resp.recent_hashrate); + printf("Threads active: %d\n", ms_resp.threads); + printf("Mining activity state: %d\n", ms_resp.mining_activity); + free((void*)ms_resp.username); + free((void*)ms_resp.time_to_reward); + increase_threads(); + sleep(.5); + decrease_threads(); + sleep(.5); + } + + pl_args.username = "cryptonote-social"; if (argc > 1) { printf("using arg for username: %s\n", argv[1]); @@ -61,6 +78,28 @@ int main(int argc, char* argv[]) { } } free((void*)pl_resp.message); + + send_chat("testing chat sending this is the chat message"); + + printf("Entering get_miner_state polling loop, 60 polls with 1 second sleep inbetween\n"); + for (int i = 0; i < 60; ++i) { + ms_resp = get_miner_state(); + printf("Hashrate was: %f\n", ms_resp.recent_hashrate); + printf("Threads active: %d\n", ms_resp.threads); + printf("Mining activity state: %d\n", ms_resp.mining_activity); + printf("Chats available: %s\n", ms_resp.chats_available ? "yes" : "no"); + if (ms_resp.chats_available) { + next_chat_response nc_resp; + nc_resp = next_chat(); + printf("Got chat message: [ %s ] %s (%ld)\n", nc_resp.username, nc_resp.message, nc_resp.timestamp); + free((void*)nc_resp.username); + free((void*)nc_resp.message); + } + free((void*)ms_resp.username); + free((void*)ms_resp.time_to_reward); + sleep(1); + } + sleep(10); printf("Setting screen state to active\n"); @@ -76,8 +115,8 @@ int main(int argc, char* argv[]) { report_power_state(false); printf("Sleeping for 2 minutes before trying another login.\n"); - sleep(60); - get_miner_state_response ms_resp = get_miner_state(); + sleep(30); + ms_resp = get_miner_state(); printf("Hashrate was: %f\n", ms_resp.recent_hashrate); printf("Threads active: %d\n", ms_resp.threads); printf("Mining activity state: %d\n", ms_resp.mining_activity); @@ -93,6 +132,14 @@ int main(int argc, char* argv[]) { printf("Hashrate was: %f\n", ms_resp.recent_hashrate); printf("Threads active: %d\n", ms_resp.threads); printf("Mining activity state: %d\n", ms_resp.mining_activity); + printf("Chats available: %s\n", ms_resp.chats_available ? "yes" : "no"); + if (ms_resp.chats_available) { + next_chat_response nc_resp; + nc_resp = next_chat(); + printf("Got chat message: [ %s ] %s (%ld)\n", nc_resp.username, nc_resp.message, nc_resp.timestamp); + free((void*)nc_resp.username); + free((void*)nc_resp.message); + } free((void*)ms_resp.username); free((void*)ms_resp.time_to_reward); sleep(1); @@ -115,8 +162,8 @@ int main(int argc, char* argv[]) { } free((void*)pl_resp.message); - printf("Sleeping for 2 minutes before looping again.\n"); - sleep(60); + printf("Sleeping for 30 sec before looping again.\n"); + sleep(30); ms_resp = get_miner_state(); printf("Hashrate was: %f\n", ms_resp.recent_hashrate); printf("Threads active: %d\n", ms_resp.threads); @@ -126,8 +173,8 @@ int main(int argc, char* argv[]) { printf("Decreasing threads\n"); decrease_threads(); - printf("Entering get_miner_state polling loop, 60 polls with 1 second sleep inbetween\n"); - for (int i = 0; i < 60; ++i) { + printf("Entering get_miner_state polling loop, 30 polls with 1 second sleep inbetween\n"); + for (int i = 0; i < 30; ++i) { ms_resp = get_miner_state(); printf("Hashrate was: %f\n", ms_resp.recent_hashrate); printf("Threads active: %d\n", ms_resp.threads); diff --git a/csminer.go b/csminer.go index ad95282..7736521 100644 --- a/csminer.go +++ b/csminer.go @@ -12,7 +12,7 @@ import ( const ( APPLICATION_NAME = "cryptonote.social Monero miner" - VERSION_STRING = "0.2.0" + VERSION_STRING = "0.3.0" STATS_WEBPAGE = "https://cryptonote.social/xmr" DONATE_USERNAME = "donate-getmonero-org" diff --git a/miner.go b/miner.go index 0298613..0e3db6f 100644 --- a/miner.go +++ b/miner.go @@ -7,10 +7,12 @@ import ( "errors" "os" "strconv" + "strings" "time" "github.com/cryptonote-social/csminer/crylog" "github.com/cryptonote-social/csminer/minerlib" + "github.com/cryptonote-social/csminer/minerlib/chat" "github.com/cryptonote-social/csminer/stratum/client" ) @@ -141,6 +143,11 @@ func Mine(c *MinerConfig) error { minerlib.RemoveMiningActivityOverride() } } + if strings.HasPrefix(b, "c ") { + chatMsg := b[2:] + chat.SendChat(chatMsg) + } + } crylog.Error("Scanning terminated") return errors.New("didn't expect keyboard scanning to terminate") @@ -220,8 +227,11 @@ func printStatsPeriodically() { } printStats(false) for { - <-time.After(30 * time.Second) - printStats(true) // print full stats only if actively mining + <-time.After(3 * time.Second) + //printStats(true) // print full stats only if actively mining + for c := chat.NextChatReceived(); c != nil; c = chat.NextChatReceived() { + crylog.Info("\n\nCHAT MESSAGE RECEIVED:\n[", c.Username, "] ", c.Message, "\n") + } } } @@ -256,6 +266,8 @@ func getActivityMessage(activityState int) string { return "ACTIVE" case minerlib.MINING_ACTIVE_USER_OVERRIDE: return "ACTIVE: keyboard override. to undo override." + case minerlib.MINING_ACTIVE_CHATS_TO_SEND: + return "ACTIVE: sending chat message." } crylog.Fatal("Unknown activity state:", activityState) if activityState > 0 { diff --git a/minerlib/chat/chat.go b/minerlib/chat/chat.go new file mode 100644 index 0000000..ddf5cd0 --- /dev/null +++ b/minerlib/chat/chat.go @@ -0,0 +1,93 @@ +package chat + +import ( + "github.com/cryptonote-social/csminer/crylog" + "github.com/cryptonote-social/csminer/stratum/client" + + "sync" +) + +var ( + mutex sync.Mutex + + chatQueue []string + chatToSendIndex int + + receivedQueue []*client.ChatResult + chatReceivedIndex int + + nextToken int +) + +func SendChat(chat string) { + mutex.Lock() + defer mutex.Unlock() + chatQueue = append(chatQueue, chat) + crylog.Info("Chat queued for sending:", chat) +} + +// GetChatToSend returns the next queued chat message that needs to be delivered. The function +// will return the same result until ChatSent is called. It will return (nil, -1) if there are no +// chats to send at this time. +func GetChatToSend() (chat string, id int) { + mutex.Lock() + defer mutex.Unlock() + if chatToSendIndex >= len(chatQueue) { + return "", -1 + } + return chatQueue[chatToSendIndex], chatToSendIndex +} + +func HasChatsToSend() bool { + mutex.Lock() + defer mutex.Unlock() + return chatToSendIndex < len(chatQueue) +} + +func ChatSent(id int) { + mutex.Lock() + defer mutex.Unlock() + if id == chatToSendIndex { + crylog.Info("Chat message delivered:", chatQueue[id]) + chatToSendIndex++ + } +} + +func ChatsReceived(chats []client.ChatResult, chatToken int, fetchedToken int) { + if len(chats) == 0 { + return + } + crylog.Info("Chats received:", chats) + mutex.Lock() + defer mutex.Unlock() + if nextToken != fetchedToken { + crylog.Warn("Skipping dupe chats:", chats) + return // these chats are already handled + } + for i := range chats { + receivedQueue = append(receivedQueue, &chats[i]) + } + nextToken = chatToken +} + +func HasChats() bool { + mutex.Lock() + defer mutex.Unlock() + return chatReceivedIndex < len(receivedQueue) +} + +func NextChatReceived() *client.ChatResult { + mutex.Lock() + defer mutex.Unlock() + if chatReceivedIndex < len(receivedQueue) { + chatReceivedIndex++ + return receivedQueue[chatReceivedIndex-1] + } + return nil +} + +func NextToken() int { + mutex.Lock() + defer mutex.Unlock() + return nextToken +} diff --git a/minerlib/minerlib.go b/minerlib/minerlib.go index 4b3b908..a6b5b44 100644 --- a/minerlib/minerlib.go +++ b/minerlib/minerlib.go @@ -3,12 +3,14 @@ package minerlib import ( "github.com/cryptonote-social/csminer/blockchain" "github.com/cryptonote-social/csminer/crylog" + "github.com/cryptonote-social/csminer/minerlib/chat" "github.com/cryptonote-social/csminer/minerlib/stats" "github.com/cryptonote-social/csminer/rx" "github.com/cryptonote-social/csminer/stratum/client" "bytes" "encoding/hex" + "encoding/json" "errors" "fmt" "runtime" @@ -46,6 +48,9 @@ const ( // Indicates miner is actively mining due to user-initiated override MINING_ACTIVE_USER_OVERRIDE = 2 + // Indicates miner is actively mining to deliver a chat message + MINING_ACTIVE_CHATS_TO_SEND = 3 + // for PokeChannel stuff: HANDLED = 1 USE_CACHED = 2 @@ -149,6 +154,10 @@ func getMiningActivityState() int { return MINING_ACTIVE_USER_OVERRIDE } + if chat.HasChatsToSend() { + return MINING_ACTIVE_CHATS_TO_SEND + } + if timeExcluded() { return MINING_PAUSED_TIME_EXCLUDED } @@ -383,6 +392,10 @@ func MiningLoop(jobChan <-chan *client.MultiClientJob, done chan<- bool) { stopWorkers() + if job.ChatToken != chat.NextToken() { + go GetChats() + } + // Check if we need to reinitialize rx dataset newSeed, err := hex.DecodeString(job.SeedHash) if err != nil { @@ -470,6 +483,7 @@ type GetMiningStateResponse struct { stats.Snapshot MiningActivity int Threads int + ChatsAvailable bool } // poke the job dispatcher to refresh recent stats. result may not be immediate but should happen @@ -505,6 +519,7 @@ func GetMiningState() *GetMiningStateResponse { Snapshot: *s, MiningActivity: as, Threads: threads, + ChatsAvailable: chat.HasChats(), } } @@ -600,6 +615,23 @@ func printStats(isMining bool) { } */ +func GetChats() { + nt := chat.NextToken() + crylog.Info("Getting chats:", nt) + resp, err := cl.GetChats(nt) + if err != nil { + crylog.Error("Failed to retrieve chats:", nt, err) + } + cr := &client.GetChatsResult{} + err = json.Unmarshal(*resp.Result, cr) + if err != nil { + crylog.Warn("Failed to unmarshal GetChatsResult:", err) + cl.Close() + return + } + chat.ChatsReceived(cr.Chats, cr.NextToken, nt) +} + func goMine(job client.MultiClientJob, thread int) { defer wg.Done() input, err := hex.DecodeString(job.Blob) @@ -631,10 +663,12 @@ func goMine(job client.MultiClientJob, thread int) { } time.Sleep(time.Second) } - resp, err := cl.SubmitWork(fnonce, jobid) + chatMsg, chatID := chat.GetChatToSend() + //crylog.Info("sending chatmsg:", chatMsg) + resp, err := cl.SubmitWork(fnonce, jobid, chatMsg, chatID) if err != nil { - cl.Close() crylog.Warn("Submit work client failure:", jobid, err) + cl.Close() return } if resp.Error != nil { @@ -642,15 +676,29 @@ func goMine(job client.MultiClientJob, thread int) { crylog.Warn("Submit work server error:", jobid, resp.Error) return } + chat.ChatSent(chatID) stats.ShareAccepted(diffTarget) - swr := resp.Result - if swr != nil { - if swr.PoolMargin > 0.0 { - stats.RefreshPoolStats2(swr) - } else { - crylog.Warn("Didn't get pool stats in response:", resp.Result) - updatePoolStats(true) - } + if resp.Result == nil { + crylog.Warn("nil result") + cl.Close() + return + } + swr := &client.SubmitWorkResult{} + err = json.Unmarshal(*resp.Result, swr) + if err != nil { + crylog.Warn("Failed to unmarshal SubmitWorkResult:", jobid, err) + cl.Close() + return + } + if swr.PoolMargin > 0.0 { + stats.RefreshPoolStats2(swr) + } else { + crylog.Warn("Didn't get pool stats in response:", resp.Result) + updatePoolStats(true) + } + //crylog.Info("Chat token:", resp.ChatToken, chat.NextToken()) + if resp.ChatToken > 0 && resp.ChatToken != chat.NextToken() { + go GetChats() } }(fnonce, job.JobID) } @@ -741,6 +789,8 @@ func getActivityMessage(activityState int) string { return "ACTIVE" case MINING_ACTIVE_USER_OVERRIDE: return "ACTIVE: user override." + case MINING_ACTIVE_CHATS_TO_SEND: + return "ACTIVE: sending chat message." } crylog.Error("Unknown activity state:", activityState) if activityState > 0 { diff --git a/stratum/client/client.go b/stratum/client/client.go index c269b55..c878b14 100644 --- a/stratum/client/client.go +++ b/stratum/client/client.go @@ -21,6 +21,7 @@ import ( const ( SUBMIT_WORK_JSON_ID = 999 CONNECT_JSON_ID = 666 + GET_CHATS_JSON_ID = 9999 MAX_REQUEST_SIZE = 50000 // Max # of bytes we will read per request @@ -35,6 +36,8 @@ type Job struct { // For self-select mode: PoolWallet string `json:"pool_wallet"` ExtraNonce string `json:"extra_nonce"` + + ChatToken int `json:"chat_token"` // custom field } type RXJob struct { @@ -72,24 +75,60 @@ type SubmitWorkResult struct { NetworkDifficulty int64 // difficulty, possibly smoothed over the last several blocks // TODO: These pool config values rarely change, so we should fetch these in some other way - // instead of returning them from each SubmitWork call. + // instead of returning them from each SubmitWork call, or perhaps only return them when + // they change. PoolMargin float64 PoolFee float64 } -type SubmitWorkResponse struct { - ID uint64 `json:"id"` - Jsonrpc string `json:"jsonrpc"` - Method string `json:"method"` - Job *MultiClientJob `json:"params"` - Result *SubmitWorkResult - Error interface{} `json:"error"` +type ChatResult struct { + Username string // user sending the chat + Message string // the chat message + Timestamp int64 // unix time in seconds when the chat message was sent +} + +type GetChatsResult struct { + Chats []ChatResult + NextToken int +} + +type Response struct { + ID uint64 `json:"id"` + Jsonrpc string `json:"jsonrpc"` + Method string `json:"method"` + + Job *MultiClientJob `json:"params"` // used to send jobs over the connection + Result *json.RawMessage `json:"result"` // used to return SubmitWork or GetChats results + + Error interface{} `json:"error"` + + ChatToken int `json:"chat_token"` // custom field +} + +type loginResponse struct { + ID uint64 `json:"id"` + Jsonrpc string `json:"jsonrpc"` + Result *struct { + ID string `json:"id"` + Job *MultiClientJob `job:"job"` + } `json:"result"` + Error *struct { + Code int `json:"code"` + Message string `json:"message"` + } `json:"error"` + // our own custom field for reporting login warnings without forcing disconnect from error: + Warning *struct { + Code int `json:"code"` + Message string `json:"message"` + } `json:"warning"` + + ChatToken int `json:"chat_token"` // custom field } type Client struct { address string conn net.Conn - responseChannel chan *SubmitWorkResponse + responseChannel chan *Response mutex sync.Mutex @@ -164,23 +203,7 @@ func (cl *Client) Connect( } // Now read the login response - response := &struct { - ID uint64 `json:"id"` - Jsonrpc string `json:"jsonrpc"` - Result *struct { - ID string `json:"id"` - Job *MultiClientJob `job:"job"` - } `json:"result"` - Error *struct { - Code int `json:"code"` - Message string `json:"message"` - } `json:"error"` - // our own custom field for reporting login warnings without forcing disconnect from error: - Warning *struct { - Code int `json:"code"` - Message string `json:"message"` - } `json:"warning"` - }{} + response := &loginResponse{} cl.conn.SetReadDeadline(time.Now().Add(30 * time.Second)) rdr := bufio.NewReaderSize(cl.conn, MAX_REQUEST_SIZE) err = readJSON(response, rdr) @@ -193,7 +216,7 @@ func (cl *Client) Connect( return errors.New("stratum server error"), response.Error.Code, response.Error.Message, nil } - cl.responseChannel = make(chan *SubmitWorkResponse) + cl.responseChannel = make(chan *Response) cl.alive = true jc := make(chan *MultiClientJob) go dispatchJobs(cl.conn, jc, response.Result.Job, cl.responseChannel) @@ -204,7 +227,7 @@ func (cl *Client) Connect( } // if error is returned then client will be closed and put in not-alive state -func (cl *Client) SubmitMulticlientWork(username string, rigid string, nonce string, connNonce []byte, jobid string, targetDifficulty int64) (*SubmitWorkResponse, error) { +func (cl *Client) SubmitMulticlientWork(username string, rigid string, nonce string, connNonce []byte, jobid string, targetDifficulty int64) (*Response, error) { submitRequest := &struct { ID uint64 `json:"id"` Method string `json:"method"` @@ -225,11 +248,11 @@ func (cl *Client) SubmitMulticlientWork(username string, rigid string, nonce str }{"696969", jobid, nonce, "", username, rigid, targetDifficulty, connNonce}, } - return cl.submitRequest(submitRequest) + return cl.submitRequest(submitRequest, SUBMIT_WORK_JSON_ID) } // if error is returned then client will be closed and put in not-alive state -func (cl *Client) submitRequest(submitRequest interface{}) (*SubmitWorkResponse, error) { +func (cl *Client) submitRequest(submitRequest interface{}, expectedResponseID uint64) (*Response, error) { cl.mutex.Lock() if !cl.alive { cl.mutex.Unlock() @@ -257,15 +280,31 @@ func (cl *Client) submitRequest(submitRequest interface{}) (*SubmitWorkResponse, crylog.Error("got nil response, closing") return nil, fmt.Errorf("submit work failure: nil response") } - if response.ID != SUBMIT_WORK_JSON_ID { - crylog.Error("got unexpected response:", response.ID, "Closing connection.") + if response.ID != expectedResponseID { + crylog.Error("got unexpected response:", response.ID, "wanted:", expectedResponseID, "Closing connection.") return nil, fmt.Errorf("submit work failure: unexpected response") } return response, nil } +func (cl *Client) GetChats(chatToken int) (*Response, error) { + chatRequest := &struct { + ID uint64 `json:"id"` + Method string `json:"method"` + Params interface{} `json:"params"` + }{ + ID: GET_CHATS_JSON_ID, + Method: "get_chats", + Params: &struct { + ChatToken int `json:"chat_token"` + }{chatToken}, + } + + return cl.submitRequest(chatRequest, GET_CHATS_JSON_ID) +} + // if error is returned then client will be closed and put in not-alive state -func (cl *Client) SubmitWork(nonce string, jobid string) (*SubmitWorkResponse, error) { +func (cl *Client) SubmitWork(nonce string, jobid string, chat string, chatID int) (*Response, error) { submitRequest := &struct { ID uint64 `json:"id"` Method string `json:"method"` @@ -278,9 +317,12 @@ func (cl *Client) SubmitWork(nonce string, jobid string) (*SubmitWorkResponse, e JobID string `json:"job_id"` Nonce string `json:"nonce"` Result string `json:"result"` - }{"696969", jobid, nonce, ""}, + + Chat string `json:"chat"` + ChatID int `json:"chat_id"` + }{"696969", jobid, nonce, "", chat, chatID}, } - return cl.submitRequest(submitRequest) + return cl.submitRequest(submitRequest, SUBMIT_WORK_JSON_ID) } func (cl *Client) Close() { @@ -293,9 +335,9 @@ func (cl *Client) Close() { cl.conn.Close() } -// DispatchJobs will forward incoming jobs to the JobChannel until error is received or the +// dispatchJobs will forward incoming jobs to the JobChannel until error is received or the // connection is closed. Client will be in not-alive state on return. -func dispatchJobs(conn net.Conn, jobChan chan<- *MultiClientJob, firstJob *MultiClientJob, responseChan chan<- *SubmitWorkResponse) { +func dispatchJobs(conn net.Conn, jobChan chan<- *MultiClientJob, firstJob *MultiClientJob, responseChan chan<- *Response) { defer func() { close(jobChan) close(responseChan) @@ -303,25 +345,26 @@ func dispatchJobs(conn net.Conn, jobChan chan<- *MultiClientJob, firstJob *Multi jobChan <- firstJob reader := bufio.NewReaderSize(conn, MAX_REQUEST_SIZE) for { - response := &SubmitWorkResponse{} + response := &Response{} conn.SetReadDeadline(time.Now().Add(3600 * time.Second)) err := readJSON(response, reader) if err != nil { - crylog.Error("readJSON failed, closing client and exiting dispatch:", err) + crylog.Error("readJSON failed, closing client:", err) break } if response.Method != "job" { - if response.ID == SUBMIT_WORK_JSON_ID { + if response.ID == SUBMIT_WORK_JSON_ID || response.ID == GET_CHATS_JSON_ID { responseChan <- response continue } - crylog.Warn("Unexpected response:", *response) + crylog.Warn("Unexpected response from stratum server. Ignoring:", *response) continue } if response.Job == nil { - crylog.Error("Didn't get job as expected:", *response) + crylog.Error("Didn't get job as expected from stratum server, closing client:", *response) break } + response.Job.ChatToken = response.ChatToken jobChan <- response.Job } }