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  }