package api import ( "encoding/json" "log" "crypto-miner-server/internal/atlas" ) func (h *WSHub) handleAgentFleetTorrentGossip(senderID string, payload json.RawMessage) { if !h.serverPolicySnapshot().FleetTorrentEnabled { return } var body struct { Records []atlas.FleetGossipRecord `json:"records"` } if err := json.Unmarshal(payload, &body); err != nil || len(body.Records) == 0 { return } records := atlas.NormalizeFleetGossipRecords(body.Records) if len(records) == 0 { return } h.relayFleetTorrentGossip(senderID, records) } func (h *WSHub) relayFleetTorrentGossip(senderID string, records []atlas.FleetGossipRecord) { out := Message{ Type: "fleet_torrent_gossip", Payload: mustMarshal(map[string]interface{}{ "records": records, "source_agent_id": senderID, }), } h.mu.RLock() defer h.mu.RUnlock() for id, ac := range h.agents { if id == senderID || ac == nil { continue } if err := ac.SendJSON(out); err != nil { log.Printf("[fleet-torrent] relay to %s: %v", id, err) } } } // subnetPrimarySeederHint elects one primary seeder per /24 when fleet torrent is enabled. func (h *WSHub) subnetPrimarySeederHint(agentID, clientIP, role string) string { if !h.serverPolicySnapshot().FleetTorrentEnabled { return "" } if role != "seeder" { return "" } subnet := subnetPrefix24(clientIP) if subnet == "" { return "" } pick := h.electSubnetPrimarySeeder(subnet) if pick == "" { return agentID } return pick } func (h *WSHub) electSubnetPrimarySeeder(subnet string) string { if subnet == "" { return "" } h.mu.RLock() defer h.mu.RUnlock() var bestID string for id, tel := range h.agentLiveTelemetry { role, _ := tel["fleet_role"].(string) if role != "seeder" { continue } ip := h.agentIPLocked(id) if subnetPrefix24(ip) != subnet { continue } if bestID == "" || id < bestID { bestID = id } } return bestID } func (h *WSHub) isSubnetPrimarySeeder(agentID, clientIP string) bool { if agentID == "" { return false } subnet := subnetPrefix24(clientIP) if subnet == "" { return false } pick := h.electSubnetPrimarySeeder(subnet) if pick == "" { return true } return pick == agentID }