github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/miningpool/session.go (about) 1 package pool 2 3 import ( 4 "encoding/binary" 5 "encoding/hex" 6 "errors" 7 "time" 8 "math" 9 "unsafe" 10 11 "sync" 12 "sync/atomic" 13 14 "SiaPrime/build" 15 "SiaPrime/persist" 16 ) 17 18 const ( 19 numSharesToAverage = 30 20 // we can drop to 1% of the highest difficulty before we decide we're disconnected 21 maxDifficultyDropRatio = 0.01 22 // how long we allow the session to linger if we haven't heard from the worker 23 heartbeatTimeout = 3 * time.Minute 24 // maxJob define how many time could exist in one session 25 maxJob = 5 26 ) 27 28 var ( 29 initialDifficulty = build.Select(build.Var{ 30 Standard: 6400.0, 31 Dev: 6400.0, 32 Testing: 0.00001, 33 }).(float64) // change from 6.0 to 1.0 34 ) 35 36 // 37 // A Session captures the interaction with a miner client from when they connect until the connection is 38 // closed. A session is tied to a single client and has many jobs associated with it 39 // 40 type Session struct { 41 ExtraNonce1 uint32 42 //authorized bool 43 authorized uint32 44 //disableVarDiff bool 45 disableVarDiff uint64 46 SessionID uint64 47 //currentDifficulty float64 48 currentDifficulty uint64 49 //highestDifficulty float64 50 highestDifficulty uint64 51 vardiff Vardiff 52 lastShareSpot uint64 53 Client *Client 54 CurrentWorker *Worker 55 CurrentShift *Shift 56 log *persist.Logger 57 CurrentJobs []*Job 58 lastJobTimestamp time.Time 59 shareTimes [numSharesToAverage]time.Time 60 lastVardiffRetarget time.Time 61 lastVardiffTimestamp time.Time 62 sessionStartTimestamp time.Time 63 lastHeartbeat time.Time 64 mu sync.RWMutex 65 66 clientVersion string 67 remoteAddr string 68 } 69 70 func newSession(p *Pool, ip string) (*Session, error) { 71 id := p.newStratumID() 72 s := &Session{ 73 //SessionID: id(), 74 ExtraNonce1: uint32(id() & 0xffffffff), 75 //currentDifficulty: initialDifficulty, 76 //highestDifficulty: initialDifficulty, 77 lastVardiffRetarget: time.Now(), 78 lastVardiffTimestamp: time.Now(), 79 //disableVarDiff: false, 80 remoteAddr: ip, 81 } 82 83 s.SetID(id()) 84 s.SetHighestDifficulty(initialDifficulty) 85 s.SetCurrentDifficulty(initialDifficulty) 86 s.SetDisableVarDiff(false) 87 s.vardiff = *s.newVardiff() 88 89 s.sessionStartTimestamp = time.Now() 90 s.SetHeartbeat() 91 92 return s, nil 93 } 94 95 func (s *Session) addClient(c *Client) { 96 atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&s.Client)), unsafe.Pointer(c)) 97 } 98 func (s *Session) GetClient() *Client { 99 return (*Client)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&s.Client)))) 100 } 101 102 func (s *Session) addWorker(w *Worker) { 103 atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&s.CurrentWorker)), unsafe.Pointer(w)) 104 w.SetSession(s) 105 } 106 func (s *Session) GetCurrentWorker() *Worker { 107 return (*Worker)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&s.CurrentWorker)))) 108 } 109 110 func (s *Session) addJob(j *Job) { 111 s.mu.Lock() 112 defer s.mu.Unlock() 113 114 if len(s.CurrentJobs) >= maxJob { 115 s.CurrentJobs = s.CurrentJobs[len(s.CurrentJobs)-maxJob+1:] 116 } 117 118 s.CurrentJobs = append(s.CurrentJobs, j) 119 s.lastJobTimestamp = time.Now() 120 } 121 122 func (s *Session) getJob(jobID uint64, nonce string) (*Job, error) { 123 s.mu.Lock() 124 defer s.mu.Unlock() 125 //s.log.Printf("submit id:%d, before pop len:%d\n", jobID, len(s.CurrentJobs)) 126 for _, j := range s.CurrentJobs { 127 // s.log.Printf("i: %d, array id: %d\n", i, j.JobID) 128 if jobID == j.JobID { 129 //s.log.Printf("get job len:%d\n", len(s.CurrentJobs)) 130 if _, ok := j.SubmitedNonce[nonce]; ok { 131 return nil, errors.New("already submited nonce for this job") 132 } 133 return j, nil 134 } 135 } 136 137 return nil, nil // for stale/unkonwn job response 138 } 139 140 func (s *Session) clearJobs() { 141 s.mu.Lock() 142 defer s.mu.Unlock() 143 s.CurrentJobs = nil 144 } 145 146 func (s *Session) addShift(shift *Shift) { 147 atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&s.CurrentShift)), unsafe.Pointer(shift)) 148 } 149 150 // Shift returns the current Shift associated with a session 151 func (s *Session) Shift() *Shift { 152 return (*Shift)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&s.CurrentShift)))) 153 } 154 155 func (s *Session) GetID() uint64 { 156 return atomic.LoadUint64(&s.SessionID) 157 } 158 159 func (s *Session) SetID(id uint64) { 160 atomic.StoreUint64(&s.SessionID, id) 161 } 162 163 func (s *Session) printID() string { 164 return sPrintID(atomic.LoadUint64(&s.SessionID)) 165 } 166 167 func (s *Session) printNonce() string { 168 extranonce1 := atomic.LoadUint32(&s.ExtraNonce1) 169 ex1 := make([]byte, 4) 170 binary.LittleEndian.PutUint32(ex1, extranonce1) 171 return hex.EncodeToString(ex1) 172 } 173 174 // SetLastShareTimestamp add a new time stamp 175 func (s *Session) SetLastShareTimestamp(t time.Time) { 176 s.mu.Lock() 177 defer s.mu.Unlock() 178 179 s.shareTimes[s.lastShareSpot] = t 180 s.lastShareSpot++ 181 if s.lastShareSpot == s.vardiff.bufSize { 182 s.lastShareSpot = 0 183 } 184 } 185 186 // ShareDurationAverage caculate the average duration of the 187 func (s *Session) ShareDurationAverage() (float64, float64) { 188 s.mu.RLock() 189 defer s.mu.RUnlock() 190 191 var minTime, maxTime time.Time 192 var timestampCount int 193 194 for i := uint64(0); i < s.vardiff.bufSize; i++ { 195 // s.log.Printf("ShareDurationAverage: %d %s %t\n", i, s.shareTimes[i], s.shareTimes[i].IsZero()) 196 if s.shareTimes[i].IsZero() { 197 continue 198 } 199 timestampCount++ 200 if minTime.IsZero() { 201 minTime = s.shareTimes[i] 202 } 203 if maxTime.IsZero() { 204 maxTime = s.shareTimes[i] 205 } 206 if s.shareTimes[i].Before(minTime) { 207 minTime = s.shareTimes[i] 208 } 209 if s.shareTimes[i].After(maxTime) { 210 maxTime = s.shareTimes[i] 211 } 212 } 213 214 var unsubmitStart time.Time 215 if maxTime.IsZero() { 216 unsubmitStart = s.sessionStartTimestamp 217 } else { 218 unsubmitStart = maxTime 219 } 220 unsubmitDuration := time.Now().Sub(unsubmitStart).Seconds() 221 if timestampCount < 2 { // less than 2 stamp 222 return unsubmitDuration, 0 223 } 224 225 historyDuration := maxTime.Sub(minTime).Seconds() / float64(timestampCount-1) 226 227 return unsubmitDuration, historyDuration 228 } 229 230 // IsStable checks if the session has been running long enough to fill up the 231 // vardiff buffer 232 func (s *Session) IsStable() bool { 233 if s.shareTimes[s.vardiff.bufSize-1].IsZero() { 234 return false 235 } 236 return true 237 } 238 239 // CurrentDifficulty returns the session's current difficulty 240 func (s *Session) CurrentDifficulty() float64 { 241 return math.Float64frombits(atomic.LoadUint64(&s.currentDifficulty)) 242 } 243 244 // HighestDifficulty returns the highest difficulty the session has seen 245 func (s *Session) HighestDifficulty() float64 { 246 return math.Float64frombits(atomic.LoadUint64(&s.highestDifficulty)) 247 } 248 249 // SetHighestDifficulty records the highest difficulty the session has seen 250 func (s *Session) SetHighestDifficulty(d float64) { 251 atomic.StoreUint64(&s.highestDifficulty, math.Float64bits(d)) 252 } 253 254 // SetCurrentDifficulty sets the current difficulty for the session 255 func (s *Session) SetCurrentDifficulty(d float64) { 256 if d > s.HighestDifficulty() { 257 d = s.HighestDifficulty() 258 } 259 atomic.StoreUint64(&s.currentDifficulty, math.Float64bits(d)) 260 } 261 262 // SetClientVersion sets the current client version for the session 263 func (s *Session) SetClientVersion(v string) { 264 s.mu.Lock() 265 defer s.mu.Unlock() 266 s.clientVersion = v 267 } 268 269 // SetDisableVarDiff sets the disable var diff flag for the session 270 func (s *Session) SetDisableVarDiff(flag bool) { 271 if flag { 272 atomic.StoreUint64(&s.disableVarDiff, 1) 273 } else { 274 atomic.StoreUint64(&s.disableVarDiff, 0) 275 } 276 } 277 278 func (s *Session) GetDisableVarDiff() bool { 279 flag := atomic.LoadUint64(&s.disableVarDiff) 280 if flag == 1 { 281 return true 282 } 283 return false 284 } 285 286 // DetectDisconnected checks to see if we haven't heard from a client for too 287 // long of a time. It does this via 2 mechanisms: 288 // 1) how long ago was the last share submitted? (the hearbeat) 289 // 2) how low has the difficulty dropped from the highest difficulty the client 290 // ever faced? 291 func (s *Session) DetectDisconnected() bool { 292 s.mu.Lock() 293 defer s.mu.Unlock() 294 if s.log != nil { 295 //s.log.Printf("time now: %s, last beat: %s, disconnect: %t\n", time.Now(),s.lastHeartbeat, time.Now().After(s.lastHeartbeat.Add(heartbeatTimeout))) 296 } 297 // disconnect if we haven't heard from the worker for a long time 298 if time.Now().After(s.lastHeartbeat.Add(heartbeatTimeout)) { 299 if s.log != nil { 300 //s.log.Printf("Disconnect because heartbeat time") 301 } 302 return true 303 } 304 // disconnect if the worker's difficulty has dropped too far from it's historical diff 305 if (s.CurrentDifficulty() / s.HighestDifficulty()) < maxDifficultyDropRatio { 306 return true 307 } 308 return false 309 } 310 311 // SetAuthorized specifies whether or not the session has been authorized 312 func (s *Session) SetAuthorized(b bool) { 313 if b { 314 atomic.StoreUint32(&s.authorized, 1) 315 } else { 316 atomic.StoreUint32(&s.authorized, 0) 317 } 318 } 319 320 // Authorized returns whether or not the session has been authorized 321 func (s *Session) Authorized() bool { 322 flag := atomic.LoadUint32(&s.authorized) 323 if flag == 1 { 324 return true 325 } 326 return false 327 } 328 329 // SetHeartbeat indicates that we just received a share submission 330 func (s *Session) SetHeartbeat() { 331 s.mu.Lock() 332 defer s.mu.Unlock() 333 s.lastHeartbeat = time.Now() 334 }