go-proxy/customdiffstats.go
Virgil 5d8d82b9b5 docs(proxy): align AX comments
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-05 02:06:24 +00:00

122 lines
2.9 KiB
Go

package proxy
import (
"strings"
"sync"
)
// CustomDiffBucketStats tracks per-custom-difficulty share outcomes.
type CustomDiffBucketStats struct {
Accepted uint64 `json:"accepted"`
Rejected uint64 `json:"rejected"`
Invalid uint64 `json:"invalid"`
Expired uint64 `json:"expired"`
HashesTotal uint64 `json:"hashes_total"`
}
// CustomDiffBuckets groups share totals by the miner's resolved custom difficulty.
//
// buckets := NewCustomDiffBuckets(true)
// buckets.OnAccept(Event{Miner: &Miner{customDiff: 50000}, Diff: 25000})
type CustomDiffBuckets struct {
enabled bool
buckets map[uint64]*CustomDiffBucketStats
mu sync.Mutex
}
// NewCustomDiffBuckets creates a per-difficulty share tracker.
func NewCustomDiffBuckets(enabled bool) *CustomDiffBuckets {
return &CustomDiffBuckets{
enabled: enabled,
buckets: make(map[uint64]*CustomDiffBucketStats),
}
}
// SetEnabled toggles recording without discarding any collected buckets.
func (b *CustomDiffBuckets) SetEnabled(enabled bool) {
if b == nil {
return
}
b.mu.Lock()
defer b.mu.Unlock()
b.enabled = enabled
}
// OnAccept records an accepted share for the miner's custom difficulty bucket.
func (b *CustomDiffBuckets) OnAccept(e Event) {
if b == nil || !b.enabled || e.Miner == nil {
return
}
b.mu.Lock()
defer b.mu.Unlock()
bucket := b.bucketLocked(e.Miner.customDiff)
bucket.Accepted++
if e.Expired {
bucket.Expired++
}
if e.Diff > 0 {
bucket.HashesTotal += e.Diff
}
}
// OnReject records a rejected share for the miner's custom difficulty bucket.
func (b *CustomDiffBuckets) OnReject(e Event) {
if b == nil || !b.enabled || e.Miner == nil {
return
}
b.mu.Lock()
defer b.mu.Unlock()
bucket := b.bucketLocked(e.Miner.customDiff)
bucket.Rejected++
if isInvalidShareReason(e.Error) {
bucket.Invalid++
}
}
// Snapshot returns a copy of the current bucket totals.
//
// summary := buckets.Snapshot()
func (b *CustomDiffBuckets) Snapshot() map[uint64]CustomDiffBucketStats {
if b == nil {
return nil
}
b.mu.Lock()
defer b.mu.Unlock()
if !b.enabled || len(b.buckets) == 0 {
return nil
}
out := make(map[uint64]CustomDiffBucketStats, len(b.buckets))
for diff, bucket := range b.buckets {
if bucket == nil {
continue
}
out[diff] = *bucket
}
return out
}
func (b *CustomDiffBuckets) bucketLocked(diff uint64) *CustomDiffBucketStats {
if b.buckets == nil {
b.buckets = make(map[uint64]*CustomDiffBucketStats)
}
bucket, ok := b.buckets[diff]
if !ok {
bucket = &CustomDiffBucketStats{}
b.buckets[diff] = bucket
}
return bucket
}
func isInvalidShareReason(reason string) bool {
reason = strings.ToLower(reason)
if reason == "" {
return false
}
return strings.Contains(reason, "low diff") ||
strings.Contains(reason, "lowdifficulty") ||
strings.Contains(reason, "low difficulty") ||
strings.Contains(reason, "malformed") ||
strings.Contains(reason, "difficulty") ||
strings.Contains(reason, "invalid") ||
strings.Contains(reason, "nonce")
}