go-proxy/pool/strategy.go
Virgil 7d2d309529 feat(proxy): close RFC gaps
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-04 10:52:30 +00:00

134 lines
3.1 KiB
Go

package pool
import (
"sync"
"time"
"dappco.re/go/core/proxy"
)
// FailoverStrategy wraps an ordered slice of PoolConfig entries.
// It connects to the first enabled pool and fails over in order on error.
// On reconnect it always retries from the primary first.
//
// strategy := pool.NewFailoverStrategy(cfg.Pools, listener, cfg)
// strategy.Connect()
type FailoverStrategy struct {
pools []proxy.PoolConfig
current int
client *StratumClient
listener StratumListener
cfg *proxy.Config
mu sync.Mutex
}
// StrategyFactory creates a new FailoverStrategy for a given StratumListener.
// Used by splitters to create per-mapper strategies without coupling to Config.
//
// factory := pool.NewStrategyFactory(cfg)
// strategy := factory(listener) // each mapper calls this
type StrategyFactory func(listener StratumListener) Strategy
// Strategy is the interface the splitters use to submit shares and check pool state.
type Strategy interface {
Connect()
Submit(jobID, nonce, result, algo string) int64
Disconnect()
IsActive() bool
}
// NewStrategyFactory captures the pool list and retry settings for later mapper creation.
//
// factory := pool.NewStrategyFactory(cfg)
func NewStrategyFactory(cfg *proxy.Config) StrategyFactory {
return func(listener StratumListener) Strategy {
if cfg == nil {
return NewFailoverStrategy(nil, listener, nil)
}
return NewFailoverStrategy(cfg.Pools, listener, cfg)
}
}
// NewFailoverStrategy stores the pool list and listener.
//
// strategy := pool.NewFailoverStrategy(cfg.Pools, listener, cfg)
func NewFailoverStrategy(pools []proxy.PoolConfig, listener StratumListener, cfg *proxy.Config) *FailoverStrategy {
return &FailoverStrategy{
pools: append([]proxy.PoolConfig(nil), pools...),
listener: listener,
cfg: cfg,
}
}
// Connect dials the current pool. On failure, advances to the next pool.
//
// strategy.Connect()
func (s *FailoverStrategy) Connect() {
s.mu.Lock()
defer s.mu.Unlock()
pools := s.pools
if s.cfg != nil {
pools = s.cfg.Pools
}
if len(pools) == 0 {
return
}
retries := 1
pause := time.Duration(0)
if s.cfg != nil {
if s.cfg.Retries > 0 {
retries = s.cfg.Retries
}
if s.cfg.RetryPause > 0 {
pause = time.Duration(s.cfg.RetryPause) * time.Second
}
}
for attempt := 0; attempt < retries; attempt++ {
for index, poolConfig := range pools {
if !poolConfig.Enabled {
continue
}
client := NewStratumClient(poolConfig, s.listener)
if errorValue := client.Connect(); errorValue == nil {
s.client = client
s.current = index
client.Login()
return
}
}
if pause > 0 && attempt < retries-1 {
time.Sleep(pause)
}
}
}
func (s *FailoverStrategy) Submit(jobID string, nonce string, result string, algo string) int64 {
s.mu.Lock()
client := s.client
s.mu.Unlock()
if client == nil {
return 0
}
return client.Submit(jobID, nonce, result, algo)
}
func (s *FailoverStrategy) Disconnect() {
s.mu.Lock()
client := s.client
s.client = nil
s.mu.Unlock()
if client != nil {
client.Disconnect()
}
}
func (s *FailoverStrategy) IsActive() bool {
s.mu.Lock()
defer s.mu.Unlock()
return s.client != nil && s.client.active
}