2026-04-05 01:30:08 +00:00
|
|
|
// Package proxy is the mining proxy library.
|
2026-04-04 11:16:28 +01:00
|
|
|
//
|
2026-04-05 01:30:08 +00:00
|
|
|
// cfg := &proxy.Config{Mode: "nicehash", Bind: []proxy.BindAddr{{Host: "0.0.0.0", Port: 3333}}, Pools: []proxy.PoolConfig{{URL: "pool.example:3333", Enabled: true}}, Workers: proxy.WorkersByRigID}
|
2026-04-04 11:16:28 +01:00
|
|
|
// p, result := proxy.New(cfg)
|
2026-04-05 01:30:08 +00:00
|
|
|
// if result.OK {
|
|
|
|
|
// p.Start()
|
|
|
|
|
// }
|
2026-04-04 11:16:28 +01:00
|
|
|
package proxy
|
|
|
|
|
|
|
|
|
|
import (
|
2026-04-04 10:29:02 +00:00
|
|
|
"net/http"
|
2026-04-04 11:16:28 +01:00
|
|
|
"sync"
|
2026-04-04 18:49:03 +00:00
|
|
|
"sync/atomic"
|
2026-04-04 11:16:28 +01:00
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
2026-04-05 02:21:48 +00:00
|
|
|
// Proxy wires the configured listeners, splitters, stats, workers, and log sinks.
|
2026-04-04 11:16:28 +01:00
|
|
|
//
|
2026-04-05 02:24:39 +00:00
|
|
|
// cfg := &proxy.Config{
|
|
|
|
|
// Mode: "nicehash",
|
|
|
|
|
// Bind: []proxy.BindAddr{{Host: "0.0.0.0", Port: 3333}},
|
|
|
|
|
// Pools: []proxy.PoolConfig{{URL: "pool.example:3333", Enabled: true}},
|
|
|
|
|
// Workers: proxy.WorkersByRigID,
|
|
|
|
|
// }
|
2026-04-05 00:42:12 +00:00
|
|
|
// p, result := proxy.New(cfg)
|
2026-04-05 01:50:02 +00:00
|
|
|
// if result.OK {
|
|
|
|
|
// p.Start()
|
|
|
|
|
// }
|
2026-04-04 11:16:28 +01:00
|
|
|
type Proxy struct {
|
2026-04-04 19:20:29 +00:00
|
|
|
config *Config
|
2026-04-05 00:45:39 +00:00
|
|
|
configMu sync.RWMutex
|
2026-04-04 19:20:29 +00:00
|
|
|
splitter Splitter
|
2026-04-05 01:08:23 +00:00
|
|
|
shareSink ShareSink
|
2026-04-04 19:20:29 +00:00
|
|
|
stats *Stats
|
|
|
|
|
workers *Workers
|
|
|
|
|
events *EventBus
|
|
|
|
|
servers []*Server
|
|
|
|
|
ticker *time.Ticker
|
|
|
|
|
watcher *ConfigWatcher
|
|
|
|
|
done chan struct{}
|
|
|
|
|
stopOnce sync.Once
|
|
|
|
|
minersMu sync.RWMutex
|
|
|
|
|
miners map[int64]*Miner
|
|
|
|
|
customDiff *CustomDiff
|
|
|
|
|
customDiffBuckets *CustomDiffBuckets
|
|
|
|
|
rateLimit *RateLimiter
|
|
|
|
|
httpServer *http.Server
|
|
|
|
|
accessLog *accessLogSink
|
2026-04-04 20:09:13 +00:00
|
|
|
shareLog *shareLogSink
|
2026-04-04 19:20:29 +00:00
|
|
|
submitCount atomic.Int64
|
2026-04-04 11:16:28 +01:00
|
|
|
}
|
|
|
|
|
|
2026-04-05 02:21:48 +00:00
|
|
|
// Splitter routes miner logins, submits, and disconnects to the active upstream strategy.
|
2026-04-05 00:25:30 +00:00
|
|
|
//
|
2026-04-05 01:46:41 +00:00
|
|
|
// splitter := nicehash.NewNonceSplitter(cfg, bus, pool.NewStrategyFactory(cfg))
|
|
|
|
|
// splitter.Connect()
|
2026-04-04 11:16:28 +01:00
|
|
|
type Splitter interface {
|
|
|
|
|
// Connect establishes the first pool upstream connection.
|
|
|
|
|
Connect()
|
|
|
|
|
// OnLogin routes a newly authenticated miner to an upstream slot.
|
|
|
|
|
OnLogin(event *LoginEvent)
|
|
|
|
|
// OnSubmit routes a share submission to the correct upstream.
|
|
|
|
|
OnSubmit(event *SubmitEvent)
|
|
|
|
|
// OnClose releases the upstream slot for a disconnecting miner.
|
|
|
|
|
OnClose(event *CloseEvent)
|
|
|
|
|
// Tick is called every second for keepalive and GC housekeeping.
|
|
|
|
|
Tick(ticks uint64)
|
|
|
|
|
// GC runs every 60 ticks to reclaim disconnected upstream slots.
|
|
|
|
|
GC()
|
|
|
|
|
// Upstreams returns current upstream pool connection counts.
|
|
|
|
|
Upstreams() UpstreamStats
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 01:08:23 +00:00
|
|
|
// ShareSink consumes share outcomes from the proxy event stream.
|
|
|
|
|
//
|
|
|
|
|
// sink.OnAccept(proxy.Event{Miner: miner, Diff: 100000})
|
|
|
|
|
// sink.OnReject(proxy.Event{Miner: miner, Error: "Invalid nonce"})
|
|
|
|
|
type ShareSink interface {
|
|
|
|
|
OnAccept(Event)
|
|
|
|
|
OnReject(Event)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 00:25:30 +00:00
|
|
|
// UpstreamStats reports pool connection counts.
|
|
|
|
|
//
|
2026-04-05 00:42:12 +00:00
|
|
|
// stats := proxy.UpstreamStats{Active: 1, Sleep: 0, Error: 0, Total: 1}
|
2026-04-04 11:16:28 +01:00
|
|
|
type UpstreamStats struct {
|
|
|
|
|
Active uint64 // connections currently receiving jobs
|
|
|
|
|
Sleep uint64 // idle connections (simple mode reuse pool)
|
|
|
|
|
Error uint64 // connections in error/reconnecting state
|
|
|
|
|
Total uint64 // Active + Sleep + Error
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 00:25:30 +00:00
|
|
|
// LoginEvent is dispatched when a miner completes login.
|
|
|
|
|
//
|
2026-04-05 00:42:12 +00:00
|
|
|
// event := proxy.LoginEvent{Miner: miner}
|
2026-04-04 11:16:28 +01:00
|
|
|
type LoginEvent struct {
|
|
|
|
|
Miner *Miner
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 00:25:30 +00:00
|
|
|
// SubmitEvent carries one miner share submission.
|
|
|
|
|
//
|
2026-04-05 00:42:12 +00:00
|
|
|
// event := proxy.SubmitEvent{Miner: miner, JobID: "job-1", Nonce: "deadbeef", Result: "HASH", RequestID: 2}
|
2026-04-04 11:16:28 +01:00
|
|
|
type SubmitEvent struct {
|
|
|
|
|
Miner *Miner
|
|
|
|
|
JobID string
|
|
|
|
|
Nonce string
|
|
|
|
|
Result string
|
|
|
|
|
Algo string
|
|
|
|
|
RequestID int64
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 00:25:30 +00:00
|
|
|
// CloseEvent is dispatched when a miner connection closes.
|
|
|
|
|
//
|
2026-04-05 00:42:12 +00:00
|
|
|
// event := proxy.CloseEvent{Miner: miner}
|
2026-04-04 11:16:28 +01:00
|
|
|
type CloseEvent struct {
|
|
|
|
|
Miner *Miner
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 02:04:03 +00:00
|
|
|
// ConfigWatcher polls a config file every second and reloads on modification.
|
2026-04-05 00:25:30 +00:00
|
|
|
//
|
2026-04-05 02:24:39 +00:00
|
|
|
// watcher := proxy.NewConfigWatcher("config.json", func(cfg *proxy.Config) {
|
|
|
|
|
// p.Reload(cfg)
|
|
|
|
|
// })
|
|
|
|
|
// watcher.Start()
|
2026-04-04 11:16:28 +01:00
|
|
|
type ConfigWatcher struct {
|
|
|
|
|
path string
|
|
|
|
|
onChange func(*Config)
|
|
|
|
|
lastMod time.Time
|
|
|
|
|
done chan struct{}
|
2026-04-05 01:56:36 +00:00
|
|
|
mu sync.Mutex
|
2026-04-05 01:59:28 +00:00
|
|
|
started bool
|
2026-04-04 11:16:28 +01:00
|
|
|
}
|
|
|
|
|
|
2026-04-05 00:25:30 +00:00
|
|
|
// RateLimiter throttles new connections per source IP.
|
|
|
|
|
//
|
2026-04-05 02:24:39 +00:00
|
|
|
// limiter := proxy.NewRateLimiter(proxy.RateLimit{
|
|
|
|
|
// MaxConnectionsPerMinute: 30,
|
|
|
|
|
// BanDurationSeconds: 300,
|
|
|
|
|
// })
|
2026-04-05 01:50:02 +00:00
|
|
|
// if limiter.Allow("1.2.3.4:3333") {
|
|
|
|
|
// // accept the socket
|
|
|
|
|
// }
|
2026-04-04 11:16:28 +01:00
|
|
|
type RateLimiter struct {
|
2026-04-04 22:06:18 +00:00
|
|
|
config RateLimit
|
2026-04-04 11:16:28 +01:00
|
|
|
buckets map[string]*tokenBucket
|
|
|
|
|
banned map[string]time.Time
|
|
|
|
|
mu sync.Mutex
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 00:25:30 +00:00
|
|
|
// tokenBucket is the per-IP refillable counter.
|
|
|
|
|
//
|
2026-04-05 00:42:12 +00:00
|
|
|
// bucket := tokenBucket{tokens: 30, lastRefill: time.Now()}
|
2026-04-04 11:16:28 +01:00
|
|
|
type tokenBucket struct {
|
|
|
|
|
tokens int
|
|
|
|
|
lastRefill time.Time
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 00:25:30 +00:00
|
|
|
// CustomDiff applies a login-time difficulty override.
|
|
|
|
|
//
|
2026-04-05 00:42:12 +00:00
|
|
|
// resolver := proxy.NewCustomDiff(50000)
|
|
|
|
|
// resolver.Apply(&Miner{user: "WALLET+75000"})
|
2026-04-04 11:16:28 +01:00
|
|
|
type CustomDiff struct {
|
2026-04-05 00:45:39 +00:00
|
|
|
globalDiff atomic.Uint64
|
2026-04-04 11:16:28 +01:00
|
|
|
}
|