fix(proxy): validate config and reload pools

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-04 18:19:09 +00:00
parent b8cf8713c5
commit 7f44596858
6 changed files with 172 additions and 9 deletions

41
config_test.go Normal file
View file

@ -0,0 +1,41 @@
package proxy
import "testing"
func TestConfig_Validate_Good(t *testing.T) {
cfg := &Config{
Mode: "nicehash",
Workers: WorkersByRigID,
Bind: []BindAddr{{Host: "0.0.0.0", Port: 3333}},
Pools: []PoolConfig{{URL: "pool.example:3333", Enabled: true}},
}
if result := cfg.Validate(); !result.OK {
t.Fatalf("expected valid config, got error: %v", result.Error)
}
}
func TestConfig_Validate_Bad(t *testing.T) {
cfg := &Config{
Workers: WorkersByRigID,
Bind: []BindAddr{{Host: "0.0.0.0", Port: 3333}},
Pools: []PoolConfig{{URL: "pool.example:3333", Enabled: true}},
}
if result := cfg.Validate(); result.OK {
t.Fatalf("expected missing mode to fail validation")
}
}
func TestConfig_Validate_Ugly(t *testing.T) {
cfg := &Config{
Mode: "nicehash",
Workers: WorkersMode("unknown"),
Bind: []BindAddr{{Host: "0.0.0.0", Port: 3333}},
Pools: []PoolConfig{{URL: "", Enabled: true}},
}
if result := cfg.Validate(); result.OK {
t.Fatalf("expected invalid workers and empty pool url to fail validation")
}
}

View file

@ -59,10 +59,6 @@ func LoadConfig(path string) (*Config, Result) {
if err := json.Unmarshal(data, cfg); err != nil {
return nil, errorResult(err)
}
if cfg.Mode == "" {
cfg.Mode = "nicehash"
}
return cfg, cfg.Validate()
}
@ -71,6 +67,12 @@ func (c *Config) Validate() Result {
if c == nil {
return errorResult(errors.New("config is nil"))
}
if !isValidMode(c.Mode) {
return errorResult(errors.New("mode must be \"nicehash\" or \"simple\""))
}
if !isValidWorkersMode(c.Workers) {
return errorResult(errors.New("workers must be one of \"rig-id\", \"user\", \"password\", \"agent\", \"ip\", or \"false\""))
}
if len(c.Bind) == 0 {
return errorResult(errors.New("bind list is empty"))
}
@ -85,6 +87,24 @@ func (c *Config) Validate() Result {
return successResult()
}
func isValidMode(mode string) bool {
switch strings.ToLower(strings.TrimSpace(mode)) {
case "nicehash", "simple":
return true
default:
return false
}
}
func isValidWorkersMode(mode WorkersMode) bool {
switch mode {
case WorkersByRigID, WorkersByUser, WorkersByPass, WorkersByAgent, WorkersByIP, WorkersDisabled:
return true
default:
return false
}
}
// NewEventBus creates an empty synchronous event dispatcher.
func NewEventBus() *EventBus {
return &EventBus{listeners: make(map[EventType][]EventHandler)}

View file

@ -338,7 +338,7 @@ func (s *FailoverStrategy) Connect() {
}
func (s *FailoverStrategy) connectLocked(start int) {
enabled := enabledPools(s.pools)
enabled := enabledPools(s.currentPools())
if len(enabled) == 0 {
return
}
@ -368,6 +368,16 @@ func (s *FailoverStrategy) connectLocked(start int) {
}
}
func (s *FailoverStrategy) currentPools() []proxy.PoolConfig {
if s == nil {
return nil
}
if s.cfg != nil && len(s.cfg.Pools) > 0 {
return s.cfg.Pools
}
return s.pools
}
// Submit sends the share through the active client.
func (s *FailoverStrategy) Submit(jobID, nonce, result, algo string) int64 {
if s == nil || s.client == nil {

27
pool/impl_test.go Normal file
View file

@ -0,0 +1,27 @@
package pool
import (
"testing"
"dappco.re/go/proxy"
)
func TestFailoverStrategy_CurrentPools_Good(t *testing.T) {
cfg := &proxy.Config{
Mode: "nicehash",
Workers: proxy.WorkersByRigID,
Bind: []proxy.BindAddr{{Host: "127.0.0.1", Port: 3333}},
Pools: []proxy.PoolConfig{{URL: "pool-a.example:3333", Enabled: true}},
}
strategy := NewFailoverStrategy(cfg.Pools, nil, cfg)
if got := len(strategy.currentPools()); got != 1 {
t.Fatalf("expected 1 pool, got %d", got)
}
cfg.Pools = []proxy.PoolConfig{{URL: "pool-b.example:4444", Enabled: true}}
if got := strategy.currentPools(); len(got) != 1 || got[0].URL != "pool-b.example:4444" {
t.Fatalf("expected current pools to follow config reload, got %+v", got)
}
}

58
reload_test.go Normal file
View file

@ -0,0 +1,58 @@
package proxy
import "testing"
func TestProxy_Reload_Good(t *testing.T) {
original := &Config{
Mode: "nicehash",
Workers: WorkersByRigID,
Bind: []BindAddr{{Host: "127.0.0.1", Port: 3333}},
Pools: []PoolConfig{{URL: "pool-a.example:3333", Enabled: true}},
}
p := &Proxy{
config: original,
customDiff: NewCustomDiff(1),
rateLimit: NewRateLimiter(RateLimit{}),
}
updated := &Config{
Mode: "simple",
Workers: WorkersByUser,
Bind: []BindAddr{{Host: "0.0.0.0", Port: 4444}},
Pools: []PoolConfig{{URL: "pool-b.example:4444", Enabled: true}},
CustomDiff: 50000,
AccessPassword: "secret",
CustomDiffStats: true,
AlgoExtension: true,
AccessLogFile: "/tmp/access.log",
ReuseTimeout: 30,
Retries: 5,
RetryPause: 2,
Watch: true,
RateLimit: RateLimit{MaxConnectionsPerMinute: 10, BanDurationSeconds: 60},
}
p.Reload(updated)
if p.config != original {
t.Fatalf("expected reload to preserve the existing config pointer")
}
if got := p.config.Bind[0]; got.Host != "127.0.0.1" || got.Port != 3333 {
t.Fatalf("expected bind addresses to remain unchanged, got %+v", got)
}
if p.config.Mode != "nicehash" {
t.Fatalf("expected mode to remain unchanged, got %q", p.config.Mode)
}
if p.config.Workers != WorkersByRigID {
t.Fatalf("expected workers mode to remain unchanged, got %q", p.config.Workers)
}
if got := p.config.Pools[0].URL; got != "pool-b.example:4444" {
t.Fatalf("expected pools to reload, got %q", got)
}
if got := p.customDiff.globalDiff; got != 50000 {
t.Fatalf("expected custom diff to reload, got %d", got)
}
if !p.rateLimit.IsActive() {
t.Fatalf("expected rate limiter to be replaced with active configuration")
}
}

View file

@ -35,9 +35,6 @@ func New(cfg *Config) (*Proxy, Result) {
if cfg == nil {
return nil, errorResult(errors.New("config is nil"))
}
if cfg.Mode == "" {
cfg.Mode = "nicehash"
}
if result := cfg.Validate(); !result.OK {
return nil, result
}
@ -227,7 +224,17 @@ func (p *Proxy) Reload(cfg *Config) {
if p == nil || cfg == nil {
return
}
p.config = cfg
if p.config == nil {
p.config = cfg
} else {
preservedBind := append([]BindAddr(nil), p.config.Bind...)
preservedMode := p.config.Mode
preservedWorkers := p.config.Workers
*p.config = *cfg
p.config.Bind = preservedBind
p.config.Mode = preservedMode
p.config.Workers = preservedWorkers
}
if p.customDiff != nil {
p.customDiff.globalDiff = cfg.CustomDiff
}