diff --git a/minerlib/chat/chat.go b/minerlib/chat/chat.go index a803cab..709028b 100644 --- a/minerlib/chat/chat.go +++ b/minerlib/chat/chat.go @@ -82,22 +82,24 @@ func ChatSent(id int64) { } } -func ChatsReceived(chats []client.ChatResult, chatToken int64, fetchedToken int64) { - if len(chats) == 0 && chatToken == fetchedToken { +// ChatsReceived should be called by whenever the server returns a GetChatsResult. tokenSent should +// be set to the value of NextToken that was used in the request to the server that produced the +// GetChatsResult response. +func ChatsReceived(cr *client.GetChatsResult, tokenSent int64) { + if len(cr.Chats) == 0 && cr.NextToken == tokenSent { return } mutex.Lock() defer mutex.Unlock() - if nextToken != fetchedToken { + if nextToken != tokenSent { // Another chat request must have succeeded before this one. - crylog.Warn("chats updated since this fetch, discarding:", chats) + crylog.Warn("chats updated since this fetch, discarding:", cr.Chats) return } - //crylog.Info("New chats received:", len(chats), chatToken, fetchedToken) - for i := range chats { - receivedQueue = append(receivedQueue, &chats[i]) + for i := range cr.Chats { + receivedQueue = append(receivedQueue, &cr.Chats[i]) } - nextToken = chatToken + nextToken = cr.NextToken } func HasChats() bool { diff --git a/minerlib/minerlib.go b/minerlib/minerlib.go index 1739f7f..76572e6 100644 --- a/minerlib/minerlib.go +++ b/minerlib/minerlib.go @@ -514,7 +514,7 @@ func RequestRecentStatsUpdate() { // dispatch loop inactive so there are no stats to update return } - go pokeJobDispatcher(UPDATE_STATS_POKE) // own gorouting so as not to block + go pokeJobDispatcher(UPDATE_STATS_POKE) // spawn goroutine so as not to block } func GetMiningState() *GetMiningStateResponse { @@ -636,8 +636,8 @@ func printStats(isMining bool) { func GetChats() { nt := chat.NextToken() - //crylog.Info("Getting chats:", nt) - resp, err := cl.GetChats(nt) + // we also request stats to be returned if they are more than a minute stale + resp, err := cl.GetChats(nt, (stats.SecondsOld() >= 60)) if err != nil { crylog.Error("Failed to retrieve chats:", nt, err) return @@ -649,7 +649,11 @@ func GetChats() { cl.Close() return } - chat.ChatsReceived(cr.Chats, cr.NextToken, nt) + chat.ChatsReceived(cr, nt) + if cr.StatsResult != nil { + crylog.Info("Got stats:", cr.StatsResult) + stats.RefreshPoolStats2(cr.StatsResult) + } } func goMine(job client.MultiClientJob, thread int) { @@ -685,7 +689,11 @@ func goMine(job client.MultiClientJob, thread int) { } chats := chat.GetChatsToSend(int64(diffTarget)) //crylog.Info("sending chatmsgs:", chats) - resp, err := cl.SubmitWork(fnonce, jobid, chats) + nt := chat.NextToken() + // Note there's a rare potential bug here if nt == 0, since a 0 token for this RPC + // indicates "don't fetch chats" for backwards compatibility with older clients. Should + // this case even occur though, it will be resolved by the chat polling loop anyway. + resp, err := cl.SubmitWork(fnonce, jobid, chats, nt) if err != nil { crylog.Warn("Submit work client failure:", jobid, err) cl.Close() @@ -713,13 +721,16 @@ func goMine(job client.MultiClientJob, thread int) { return } if swr.PoolMargin > 0.0 { - stats.RefreshPoolStats2(swr) + tmp := &swr.StatsResult + stats.RefreshPoolStats2(tmp) } else { + // This shouldn't ever happen if the server is behaving appropriately. crylog.Warn("Didn't get pool stats in response:", resp.Result) updatePoolStats(true) } - if resp.ChatToken != chat.NextToken() { - go GetChats() + if swr.ChatsResult != nil { + crylog.Info("Got chats:", swr.ChatsResult) + chat.ChatsReceived(swr.ChatsResult, nt) } }(fnonce, job.JobID) } diff --git a/minerlib/stats/stats.go b/minerlib/stats/stats.go index 671bba2..88d3c1e 100644 --- a/minerlib/stats/stats.go +++ b/minerlib/stats/stats.go @@ -55,6 +55,19 @@ func Init() { } } +func SecondsOld() int { + mutex.Lock() + defer mutex.Unlock() + return secondsOld() +} + +func secondsOld() int { + if lastPoolUpdateTime.IsZero() { + return -1 + } + return int(time.Now().Sub(lastPoolUpdateTime).Seconds()) +} + // Call whenever we're at at a point where recent hashrate calculation would be accurate, // e.g. after all worker threads have been tallied. func RecentStatsNowAccurate() { @@ -154,15 +167,11 @@ func GetSnapshot(isMining bool) (s *Snapshot, secondsSinceReset float64, seconds r.Accumulated = accumulated r.TimeToReward = timeToReward } - if lastPoolUpdateTime.IsZero() { - r.SecondsOld = -1.0 - } else { - r.SecondsOld = int(time.Now().Sub(lastPoolUpdateTime).Seconds()) - } + r.SecondsOld = secondsOld() return r, time.Now().Sub(recentStatsResetTime).Seconds(), elapsedRecent } -func RefreshPoolStats2(swr *client.SubmitWorkResult) { +func RefreshPoolStats2(swr *client.StatsResult) { diff := float64(swr.NetworkDifficulty) hr := float64(swr.PPROPHashrate) var ttreward string diff --git a/stratum/client/client.go b/stratum/client/client.go index f2d0e9a..77aed9d 100644 --- a/stratum/client/client.go +++ b/stratum/client/client.go @@ -63,9 +63,7 @@ type MultiClientJob struct { ConnNonce uint32 `json:"nonce"` } -type SubmitWorkResult struct { - Status string `json:"status"` - +type StatsResult struct { Progress float64 // progress of this user LifetimeHashes int64 // hashes from this user over its lifetime Paid float64 // Total crypto paid to this user over its lifetime. @@ -85,6 +83,16 @@ type SubmitWorkResult struct { PoolFee float64 } +type SubmitWorkResult struct { + Status string `json:"status"` + StatsResult + + // ChatsResult is for returning new chats in the response, but only if explicitly requested by + // setting a non-zero chat_token in the request. Will be nil if chats were not requested or if + // there are no new chats. + ChatsResult *GetChatsResult +} + type ChatResult struct { Username string // user sending the chat Message string // the chat message @@ -95,6 +103,11 @@ type ChatResult struct { type GetChatsResult struct { Chats []ChatResult NextToken int64 + + // StatsResult is for also returning updated stats, but only if explicitly requested by setting + // update_stats to true in the request. This can be used to avoid having to make another round + // trip to also update stats. + StatsResult *StatsResult } type Response struct { @@ -293,7 +306,8 @@ func (cl *Client) submitRequest(submitRequest interface{}, expectedResponseID ui return response, nil } -func (cl *Client) GetChats(chatToken int64) (*Response, error) { +// If updateStats is true, then get_chats will also return the lastest user stats. +func (cl *Client) GetChats(chatToken int64, updateStats bool) (*Response, error) { chatRequest := &struct { ID uint64 `json:"id"` Method string `json:"method"` @@ -302,8 +316,9 @@ func (cl *Client) GetChats(chatToken int64) (*Response, error) { ID: GET_CHATS_JSON_ID, Method: "get_chats", Params: &struct { - ChatToken int64 `json:"chat_token"` - }{chatToken}, + ChatToken int64 `json:"chat_token"` + UpdateStats bool `json:"update_stats"` // if true, then return update stats results too + }{chatToken, updateStats}, } return cl.submitRequest(chatRequest, GET_CHATS_JSON_ID) @@ -314,8 +329,9 @@ type ChatToSend struct { Message string } -// if error is returned then client will be closed and put in not-alive state -func (cl *Client) SubmitWork(nonce string, jobid string, chats []ChatToSend) (*Response, error) { +// If chatToken is non-zero then submit_work will return new chats, if there are any. If error is +// returned by this method, then client will be closed and put in not-alive state. +func (cl *Client) SubmitWork(nonce string, jobid string, chats []ChatToSend, chatToken int64) (*Response, error) { submitRequest := &struct { ID uint64 `json:"id"` Method string `json:"method"` @@ -329,8 +345,9 @@ func (cl *Client) SubmitWork(nonce string, jobid string, chats []ChatToSend) (*R Nonce string `json:"nonce"` Result string `json:"result"` - Chats []ChatToSend `json:"chats"` - }{"696969", jobid, nonce, "", chats}, + Chats []ChatToSend `json:"chats"` + ChatToken int64 `json:"chat_token"` // if non-zero, then return any new chats too + }{"696969", jobid, nonce, "", chats, chatToken}, } return cl.submitRequest(submitRequest, SUBMIT_WORK_JSON_ID) }