Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
164 lines
3.7 KiB
Go
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
|
|
}
|