Files
AetherForge/server/internal/api/fleet_role.go
AetherForge 7b2d41cda8
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Expand P2 test coverage: mining chain, spread lanes, path forge, WS/beacon, E2E onion, file handling
2026-06-07 04:58:55 -07:00

164 lines
3.7 KiB
Go

package api
import (
"crypto/sha256"
"encoding/hex"
"strings"
"crypto-miner-server/internal/strategy"
)
// LANSeederHint is pushed to miners on auth when fleet roles are enabled.
type LANSeederHint struct {
AgentID string `json:"agent_id"`
IP string `json:"ip,omitempty"`
LANFallbackURL string `json:"lan_fallback_url,omitempty"`
}
func normalizeFleetRole(role string) string {
switch strings.ToLower(strings.TrimSpace(role)) {
case "seeder":
return "seeder"
case "miner":
return "miner"
default:
return "auto"
}
}
func subnetPrefix24(ip string) string {
ip = strings.TrimSpace(ip)
if idx := strings.LastIndex(ip, ":"); idx > 0 && strings.Count(ip, ":") == 1 {
ip = ip[:idx]
}
parts := strings.Split(ip, ".")
if len(parts) < 3 {
return ""
}
return parts[0] + "." + parts[1] + "." + parts[2]
}
func (h *WSHub) storeAgentFleetRole(agentID, role string) {
role = normalizeFleetRole(role)
if role == "auto" {
role = "miner"
}
h.mu.Lock()
tel, ok := h.agentLiveTelemetry[agentID]
if !ok {
tel = map[string]interface{}{}
h.agentLiveTelemetry[agentID] = tel
}
tel["fleet_role"] = role
h.mu.Unlock()
}
func (h *WSHub) fleetRoleHintForAuth(agentID, bakedRole, clientIP string, seederCapable bool) string {
if !h.serverPolicySnapshot().FleetRolesEnabled {
return ""
}
baked := normalizeFleetRole(bakedRole)
if baked == "seeder" || baked == "miner" {
return baked
}
subnet := subnetPrefix24(clientIP)
if h.subnetHasOnlineSeeder(subnet) {
return "miner"
}
if seederCapable && h.shouldElectSubnetSeeder(agentID, subnet) {
return "seeder"
}
return "miner"
}
func (h *WSHub) subnetHasOnlineSeeder(subnet string) bool {
if subnet == "" {
return false
}
h.mu.RLock()
defer h.mu.RUnlock()
for id, tel := range h.agentLiveTelemetry {
role, _ := tel["fleet_role"].(string)
if role != "seeder" {
continue
}
if ac, ok := h.agents[id]; ok && ac != nil {
_ = ac
if agentIP := h.agentIPLocked(id); subnetPrefix24(agentIP) == subnet {
return true
}
}
}
return false
}
func (h *WSHub) shouldElectSubnetSeeder(agentID, subnet string) bool {
if subnet == "" {
return false
}
sum := sha256.Sum256([]byte(subnet))
pick := hex.EncodeToString(sum[:4])
return strings.HasPrefix(agentID, pick[:2]) || pick[0]%3 == 0
}
func (h *WSHub) agentIPLocked(agentID string) string {
if ag, err := h.db.GetAgent(agentID); err == nil && ag != nil {
return ag.IP
}
return ""
}
func (h *WSHub) lanSeedersForMiner(clientIP string) []LANSeederHint {
if !h.serverPolicySnapshot().FleetRolesEnabled {
return nil
}
subnet := subnetPrefix24(clientIP)
var out []LANSeederHint
h.mu.RLock()
for id, tel := range h.agentLiveTelemetry {
role, _ := tel["fleet_role"].(string)
if role != "seeder" {
continue
}
ip := h.agentIPLocked(id)
if subnet != "" && subnetPrefix24(ip) != subnet {
continue
}
fallback := ""
if ip != "" {
fallback = "http://" + ip + ":8989/api/v1/public/webrtc-mesh/manifest?seeder=" + id
}
out = append(out, LANSeederHint{AgentID: id, IP: ip, LANFallbackURL: fallback})
}
h.mu.RUnlock()
return out
}
func (h *WSHub) ingestFleetPressure(agentID string, broadcast map[string]interface{}) {
role, _ := broadcast["fleet_role"].(string)
if role != "" {
h.storeAgentFleetRole(agentID, role)
}
seed, seedOK := broadcast["seed_pressure"].(float64)
hr, hrOK := broadcast["hashrate_pressure"].(float64)
if !seedOK && !hrOK {
return
}
heat := strategy.EmberwakeHeat(role, seed, hr)
h.mu.Lock()
tel, ok := h.agentLiveTelemetry[agentID]
if !ok {
tel = map[string]interface{}{}
h.agentLiveTelemetry[agentID] = tel
}
if seedOK {
tel["seed_pressure"] = seed
}
if hrOK {
tel["hashrate_pressure"] = hr
}
tel["emberwake_heat"] = heat
h.mu.Unlock()
broadcast["emberwake_heat"] = heat
}