216 lines
4.7 KiB
Go
216 lines
4.7 KiB
Go
// Package nicehash implements the NiceHash nonce-splitting mode.
|
|
//
|
|
// It partitions the 32-bit nonce space among miners by fixing one byte (byte 39
|
|
// of the blob). Each upstream pool connection (NonceMapper) owns a 256-slot table.
|
|
// Up to 256 miners share one pool connection. The 257th miner triggers creation
|
|
// of a new NonceMapper with a new pool connection.
|
|
//
|
|
// s := nicehash.NewNonceSplitter(cfg, eventBus, strategyFactory)
|
|
// s.Connect()
|
|
package nicehash
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
|
|
"dappco.re/go/core/proxy"
|
|
"dappco.re/go/core/proxy/pool"
|
|
)
|
|
|
|
// NonceSplitter is the Splitter implementation for NiceHash mode.
|
|
// It manages a dynamic slice of NonceMapper upstreams, creating new ones on demand.
|
|
//
|
|
// s := nicehash.NewNonceSplitter(cfg, eventBus, strategyFactory)
|
|
// s.Connect()
|
|
type NonceSplitter struct {
|
|
mappers []*NonceMapper
|
|
cfg *proxy.Config
|
|
events *proxy.EventBus
|
|
strategyFactory pool.StrategyFactory
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// NewNonceSplitter creates the NiceHash splitter.
|
|
//
|
|
// s := nicehash.NewNonceSplitter(cfg, bus, factory)
|
|
func NewNonceSplitter(cfg *proxy.Config, events *proxy.EventBus, factory pool.StrategyFactory) *NonceSplitter {
|
|
return &NonceSplitter{
|
|
cfg: cfg,
|
|
events: events,
|
|
strategyFactory: factory,
|
|
mappers: make([]*NonceMapper, 0, 1),
|
|
}
|
|
}
|
|
|
|
func (s *NonceSplitter) Connect() {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if len(s.mappers) > 0 {
|
|
return
|
|
}
|
|
|
|
mapper := s.newMapperLocked()
|
|
s.mappers = append(s.mappers, mapper)
|
|
mapper.strategy.Connect()
|
|
}
|
|
|
|
func (s *NonceSplitter) OnLogin(event *proxy.LoginEvent) {
|
|
if event == nil || event.Miner == nil {
|
|
return
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
for _, mapper := range s.mappers {
|
|
if mapper.Add(event.Miner) {
|
|
mapper.events = s.events
|
|
event.Miner.SetMapperID(mapper.id)
|
|
event.Miner.SetNiceHashEnabled(true)
|
|
if currentJob := mapper.storage.CurrentJob(); currentJob != nil && currentJob.IsValid() {
|
|
event.Miner.PrimeJob(*currentJob)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
|
|
mapper := s.newMapperLocked()
|
|
s.mappers = append(s.mappers, mapper)
|
|
mapper.strategy.Connect()
|
|
if mapper.Add(event.Miner) {
|
|
mapper.events = s.events
|
|
event.Miner.SetMapperID(mapper.id)
|
|
event.Miner.SetNiceHashEnabled(true)
|
|
if currentJob := mapper.storage.CurrentJob(); currentJob != nil && currentJob.IsValid() {
|
|
event.Miner.PrimeJob(*currentJob)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *NonceSplitter) OnSubmit(event *proxy.SubmitEvent) {
|
|
if event == nil || event.Miner == nil {
|
|
return
|
|
}
|
|
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
for _, mapper := range s.mappers {
|
|
if mapper.id == event.Miner.MapperID() {
|
|
mapper.Submit(event)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *NonceSplitter) OnClose(event *proxy.CloseEvent) {
|
|
if event == nil || event.Miner == nil {
|
|
return
|
|
}
|
|
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
for _, mapper := range s.mappers {
|
|
if mapper.id == event.Miner.MapperID() {
|
|
mapper.Remove(event.Miner)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *NonceSplitter) Tick(ticks uint64) {
|
|
if ticks%60 == 0 {
|
|
s.GC()
|
|
}
|
|
}
|
|
|
|
func (s *NonceSplitter) GC() {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
now := time.Now().UTC()
|
|
filtered := s.mappers[:0]
|
|
for _, mapper := range s.mappers {
|
|
_, _, active := mapper.storage.SlotCount()
|
|
if active == 0 && mapper.IdleDuration(now) >= 60*time.Second {
|
|
if mapper.strategy != nil {
|
|
mapper.strategy.Disconnect()
|
|
}
|
|
continue
|
|
}
|
|
filtered = append(filtered, mapper)
|
|
}
|
|
s.mappers = filtered
|
|
}
|
|
|
|
func (s *NonceSplitter) Upstreams() proxy.UpstreamStats {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
var stats proxy.UpstreamStats
|
|
for _, mapper := range s.mappers {
|
|
stats.Total++
|
|
switch {
|
|
case mapper.suspended > 0:
|
|
stats.Error++
|
|
case mapper.IsActive():
|
|
stats.Active++
|
|
default:
|
|
stats.Sleep++
|
|
}
|
|
}
|
|
return stats
|
|
}
|
|
|
|
func (s *NonceSplitter) PendingCount() int {
|
|
s.mu.RLock()
|
|
mappers := append([]*NonceMapper(nil), s.mappers...)
|
|
s.mu.RUnlock()
|
|
|
|
pending := 0
|
|
for _, mapper := range mappers {
|
|
if mapper == nil {
|
|
continue
|
|
}
|
|
mapper.mu.Lock()
|
|
pending += len(mapper.pending)
|
|
mapper.mu.Unlock()
|
|
}
|
|
return pending
|
|
}
|
|
|
|
func (s *NonceSplitter) Disconnect() {
|
|
s.mu.Lock()
|
|
mappers := s.mappers
|
|
s.mappers = nil
|
|
s.mu.Unlock()
|
|
|
|
for _, mapper := range mappers {
|
|
if mapper == nil {
|
|
continue
|
|
}
|
|
mapper.mu.Lock()
|
|
strategy := mapper.strategy
|
|
mapper.strategy = nil
|
|
mapper.active = false
|
|
mapper.suspended = 0
|
|
mapper.mu.Unlock()
|
|
if strategy != nil {
|
|
strategy.Disconnect()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *NonceSplitter) newMapperLocked() *NonceMapper {
|
|
mapperID := int64(len(s.mappers) + 1)
|
|
mapper := NewNonceMapper(mapperID, s.cfg, nil)
|
|
mapper.events = s.events
|
|
var strategy pool.Strategy
|
|
if s.strategyFactory != nil {
|
|
strategy = s.strategyFactory(mapper)
|
|
}
|
|
mapper.strategy = strategy
|
|
return mapper
|
|
}
|