Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Agents read vpc-id from EC2 IMDS on auth; the server scopes subnet_primary_seeder to vpc-id with /24 fallback, exposes VPC seeder badges, and documents cross-VPC gossip via peering/TGW.
480 lines
12 KiB
Go
480 lines
12 KiB
Go
// Package spreadrouter computes BGP-style spread routes from Path Tracer sessions,
|
||
// agent clearance, lane success history, and latency.
|
||
package spreadrouter
|
||
|
||
import (
|
||
"net"
|
||
"strings"
|
||
|
||
"crypto-miner-server/internal/clearance"
|
||
)
|
||
|
||
// SpreadMinClearance is the minimum clearance required for spread actions (L2).
|
||
const SpreadMinClearance = clearance.L2
|
||
|
||
// HopSnapshot is one agent hop in an active Path Tracer session.
|
||
type HopSnapshot struct {
|
||
AgentID string
|
||
AgentName string
|
||
Subnet string
|
||
SessionID string
|
||
HopIndex int
|
||
Connected bool
|
||
}
|
||
|
||
// SubnetDiscovery links a discovering hop to hosts on a target subnet.
|
||
type SubnetDiscovery struct {
|
||
Subnet string
|
||
AgentID string
|
||
Hosts []string
|
||
}
|
||
|
||
// SessionSnapshot is routable state from one Path Tracer session.
|
||
type SessionSnapshot struct {
|
||
SessionID string
|
||
Hops []HopSnapshot
|
||
Discoveries []SubnetDiscovery
|
||
}
|
||
|
||
// FleetAgentSnapshot is a connected fleet agent used for routing.
|
||
type FleetAgentSnapshot struct {
|
||
AgentID string
|
||
AgentName string
|
||
Subnet string
|
||
Clearance int
|
||
LatencyMs int
|
||
JoinLane string
|
||
Connected bool
|
||
}
|
||
|
||
// LaneSuccessStat is historical join-lane success on a subnet.
|
||
type LaneSuccessStat struct {
|
||
Subnet string
|
||
JoinLane string
|
||
Success int
|
||
}
|
||
|
||
// Input feeds the route table builder.
|
||
type Input struct {
|
||
Sessions []SessionSnapshot
|
||
FleetAgents []FleetAgentSnapshot
|
||
LaneSuccess []LaneSuccessStat
|
||
TargetSubnets []string
|
||
RequestedLane string
|
||
ErasureLanesEnabled bool
|
||
}
|
||
|
||
// RouteEdge is a weighted edge from a seed hop to a target subnet.
|
||
type RouteEdge struct {
|
||
FromAgentID string `json:"from_agent_id"`
|
||
FromAgentName string `json:"from_agent_name,omitempty"`
|
||
ToSubnet string `json:"to_subnet"`
|
||
SessionID string `json:"session_id,omitempty"`
|
||
HopIndex int `json:"hop_index,omitempty"`
|
||
JoinLane string `json:"join_lane,omitempty"`
|
||
Clearance int `json:"clearance_level"`
|
||
LaneSuccess float64 `json:"lane_success_rate"`
|
||
LatencyMs int `json:"latency_ms,omitempty"`
|
||
Weight float64 `json:"weight"`
|
||
}
|
||
|
||
// RouteRecommendation is the best seed hop for one target subnet.
|
||
type RouteRecommendation struct {
|
||
TargetSubnet string `json:"target_subnet"`
|
||
SeedAgentID string `json:"seed_agent_id"`
|
||
SeedAgentName string `json:"seed_agent_name,omitempty"`
|
||
EgressAgentID string `json:"egress_agent_id"`
|
||
EgressHopIndex int `json:"egress_hop_index,omitempty"`
|
||
SessionID string `json:"session_id,omitempty"`
|
||
JoinLane string `json:"join_lane,omitempty"`
|
||
ClearanceLevel int `json:"clearance_level"`
|
||
Score float64 `json:"score"`
|
||
Reason string `json:"reason,omitempty"`
|
||
ErasureLanesEnabled bool `json:"erasure_lanes_enabled,omitempty"`
|
||
SwarmMagnet string `json:"swarm_magnet,omitempty"`
|
||
ShardManifestURLs []string `json:"shard_manifest_urls,omitempty"`
|
||
}
|
||
|
||
// SpreadRouteHint is attached to signed deploy plans for agent egress routing.
|
||
type SpreadRouteHint struct {
|
||
TargetSubnet string `json:"target_subnet"`
|
||
SeedAgentID string `json:"seed_agent_id"`
|
||
SeedAgentName string `json:"seed_agent_name,omitempty"`
|
||
EgressAgentID string `json:"egress_agent_id"`
|
||
EgressHopIndex int `json:"egress_hop_index,omitempty"`
|
||
SessionID string `json:"session_id,omitempty"`
|
||
JoinLane string `json:"join_lane,omitempty"`
|
||
Score float64 `json:"score,omitempty"`
|
||
ClearanceLevel int `json:"clearance_level,omitempty"`
|
||
// ErasureLanesEnabled signals parallel Reed–Solomon lane redundancy on deploy plans.
|
||
ErasureLanesEnabled bool `json:"erasure_lanes_enabled,omitempty"`
|
||
// SwarmMagnet is the fleet torrent magnet link for erasure shard swarm discovery.
|
||
SwarmMagnet string `json:"swarm_magnet,omitempty"`
|
||
// ShardManifestURLs lists C2/public shard fetch URLs for BGP spread hints.
|
||
ShardManifestURLs []string `json:"shard_manifest_urls,omitempty"`
|
||
}
|
||
|
||
// RouteTable holds weighted edges and recommendations.
|
||
type RouteTable struct {
|
||
Edges []RouteEdge
|
||
Routes []RouteRecommendation
|
||
bySubnet map[string]RouteRecommendation
|
||
}
|
||
|
||
const (
|
||
weightClearance = 0.35
|
||
weightLane = 0.40
|
||
weightLatency = 0.25
|
||
)
|
||
|
||
type candidate struct {
|
||
agentID string
|
||
agentName string
|
||
subnet string
|
||
sessionID string
|
||
hopIndex int
|
||
clearance int
|
||
latencyMs int
|
||
joinLane string
|
||
laneRate float64
|
||
discovered bool
|
||
}
|
||
|
||
// Build constructs a route table from Path Tracer sessions and fleet telemetry.
|
||
func Build(in Input) *RouteTable {
|
||
rt := &RouteTable{bySubnet: make(map[string]RouteRecommendation)}
|
||
laneRates := laneSuccessRates(in.LaneSuccess, in.RequestedLane)
|
||
fleetByID := make(map[string]FleetAgentSnapshot, len(in.FleetAgents))
|
||
for _, ag := range in.FleetAgents {
|
||
fleetByID[ag.AgentID] = ag
|
||
}
|
||
|
||
targets := normalizeTargets(in)
|
||
for _, target := range targets {
|
||
cands := collectCandidates(in, target, fleetByID, laneRates)
|
||
rec, edges := scoreCandidates(target, in.RequestedLane, in.ErasureLanesEnabled, cands)
|
||
if rec.SeedAgentID != "" {
|
||
rt.Routes = append(rt.Routes, rec)
|
||
rt.bySubnet[target] = rec
|
||
}
|
||
rt.Edges = append(rt.Edges, edges...)
|
||
}
|
||
return rt
|
||
}
|
||
|
||
// Recommend returns the best route for a target subnet.
|
||
func (rt *RouteTable) Recommend(targetSubnet string) (RouteRecommendation, bool) {
|
||
if rt == nil {
|
||
return RouteRecommendation{}, false
|
||
}
|
||
targetSubnet = NormalizeSubnet(targetSubnet)
|
||
rec, ok := rt.bySubnet[targetSubnet]
|
||
return rec, ok
|
||
}
|
||
|
||
// ToHint converts a recommendation into a deploy-plan hint.
|
||
func ToHint(rec RouteRecommendation) *SpreadRouteHint {
|
||
if rec.SeedAgentID == "" {
|
||
return nil
|
||
}
|
||
return &SpreadRouteHint{
|
||
TargetSubnet: rec.TargetSubnet,
|
||
SeedAgentID: rec.SeedAgentID,
|
||
SeedAgentName: rec.SeedAgentName,
|
||
EgressAgentID: rec.EgressAgentID,
|
||
EgressHopIndex: rec.EgressHopIndex,
|
||
SessionID: rec.SessionID,
|
||
JoinLane: rec.JoinLane,
|
||
Score: rec.Score,
|
||
ClearanceLevel: rec.ClearanceLevel,
|
||
ErasureLanesEnabled: rec.ErasureLanesEnabled,
|
||
}
|
||
}
|
||
|
||
func normalizeTargets(in Input) []string {
|
||
seen := make(map[string]bool)
|
||
var out []string
|
||
add := func(s string) {
|
||
s = NormalizeSubnet(s)
|
||
if s == "" || seen[s] {
|
||
return
|
||
}
|
||
seen[s] = true
|
||
out = append(out, s)
|
||
}
|
||
for _, t := range in.TargetSubnets {
|
||
add(t)
|
||
}
|
||
for _, sess := range in.Sessions {
|
||
for _, d := range sess.Discoveries {
|
||
add(d.Subnet)
|
||
}
|
||
}
|
||
for _, ag := range in.FleetAgents {
|
||
add(ag.Subnet)
|
||
}
|
||
return out
|
||
}
|
||
|
||
func collectCandidates(in Input, target string, fleet map[string]FleetAgentSnapshot, laneRates map[string]float64) []candidate {
|
||
seen := make(map[string]bool)
|
||
var out []candidate
|
||
|
||
add := func(c candidate) {
|
||
if c.agentID == "" {
|
||
return
|
||
}
|
||
if ag, ok := fleet[c.agentID]; ok {
|
||
if c.clearance == 0 {
|
||
c.clearance = ag.Clearance
|
||
}
|
||
if c.latencyMs == 0 {
|
||
c.latencyMs = ag.LatencyMs
|
||
}
|
||
if c.joinLane == "" {
|
||
c.joinLane = ag.JoinLane
|
||
}
|
||
if c.agentName == "" {
|
||
c.agentName = ag.AgentName
|
||
}
|
||
if !ag.Connected {
|
||
return
|
||
}
|
||
}
|
||
if c.clearance < SpreadMinClearance {
|
||
return
|
||
}
|
||
key := c.agentID + "|" + target
|
||
if seen[key] {
|
||
return
|
||
}
|
||
seen[key] = true
|
||
if c.laneRate == 0 {
|
||
c.laneRate = laneRates[target+"|"+strings.TrimSpace(c.joinLane)]
|
||
if c.laneRate == 0 {
|
||
c.laneRate = laneRates[target+"|"]
|
||
}
|
||
}
|
||
out = append(out, c)
|
||
}
|
||
|
||
for _, sess := range in.Sessions {
|
||
for _, hop := range sess.Hops {
|
||
ag := fleet[hop.AgentID]
|
||
c := candidate{
|
||
agentID: hop.AgentID,
|
||
agentName: hop.AgentName,
|
||
subnet: hop.Subnet,
|
||
sessionID: sess.SessionID,
|
||
hopIndex: hop.HopIndex,
|
||
clearance: ag.Clearance,
|
||
latencyMs: ag.LatencyMs,
|
||
joinLane: ag.JoinLane,
|
||
}
|
||
for _, d := range sess.Discoveries {
|
||
if NormalizeSubnet(d.Subnet) == target && d.AgentID == hop.AgentID {
|
||
c.discovered = true
|
||
break
|
||
}
|
||
}
|
||
if c.discovered || NormalizeSubnet(hop.Subnet) == target {
|
||
add(c)
|
||
}
|
||
}
|
||
}
|
||
|
||
for _, ag := range in.FleetAgents {
|
||
if !ag.Connected || ag.Clearance < SpreadMinClearance {
|
||
continue
|
||
}
|
||
if NormalizeSubnet(ag.Subnet) != target {
|
||
continue
|
||
}
|
||
add(candidate{
|
||
agentID: ag.AgentID,
|
||
agentName: ag.AgentName,
|
||
subnet: ag.Subnet,
|
||
clearance: ag.Clearance,
|
||
latencyMs: ag.LatencyMs,
|
||
joinLane: ag.JoinLane,
|
||
laneRate: laneRates[target+"|"+strings.TrimSpace(ag.JoinLane)],
|
||
})
|
||
}
|
||
return out
|
||
}
|
||
|
||
func scoreCandidates(target, requestedLane string, erasureLanes bool, cands []candidate) (RouteRecommendation, []RouteEdge) {
|
||
var edges []RouteEdge
|
||
var best RouteRecommendation
|
||
var bestScore float64
|
||
|
||
for _, c := range cands {
|
||
clearanceScore := clearancePreference(c.clearance)
|
||
laneScore := c.laneRate
|
||
if laneScore <= 0 && strings.TrimSpace(requestedLane) != "" && strings.EqualFold(c.joinLane, requestedLane) {
|
||
laneScore = 0.5
|
||
}
|
||
latencyScore := latencyPreference(c.latencyMs)
|
||
weight := weightClearance*clearanceScore + weightLane*laneScore + weightLatency*latencyScore
|
||
if c.discovered {
|
||
weight += 0.05
|
||
}
|
||
|
||
edges = append(edges, RouteEdge{
|
||
FromAgentID: c.agentID,
|
||
FromAgentName: c.agentName,
|
||
ToSubnet: target,
|
||
SessionID: c.sessionID,
|
||
HopIndex: c.hopIndex,
|
||
JoinLane: c.joinLane,
|
||
Clearance: c.clearance,
|
||
LaneSuccess: laneScore,
|
||
LatencyMs: c.latencyMs,
|
||
Weight: weight,
|
||
})
|
||
|
||
if weight > bestScore {
|
||
bestScore = weight
|
||
reason := "minimum-clearance route"
|
||
if c.discovered {
|
||
reason = "path-tracer discovery on subnet"
|
||
} else if NormalizeSubnet(c.subnet) == target {
|
||
reason = "fleet agent on target subnet"
|
||
}
|
||
best = RouteRecommendation{
|
||
TargetSubnet: target,
|
||
SeedAgentID: c.agentID,
|
||
SeedAgentName: c.agentName,
|
||
EgressAgentID: c.agentID,
|
||
EgressHopIndex: c.hopIndex,
|
||
SessionID: c.sessionID,
|
||
JoinLane: firstNonEmpty(requestedLane, c.joinLane),
|
||
ClearanceLevel: c.clearance,
|
||
Score: weight,
|
||
Reason: reason,
|
||
ErasureLanesEnabled: erasureLanes,
|
||
}
|
||
}
|
||
}
|
||
return best, edges
|
||
}
|
||
|
||
func clearancePreference(level int) float64 {
|
||
if level < SpreadMinClearance {
|
||
return 0
|
||
}
|
||
excess := float64(level - SpreadMinClearance)
|
||
maxExcess := float64(clearance.L4 - SpreadMinClearance)
|
||
if maxExcess <= 0 {
|
||
return 1
|
||
}
|
||
if excess > maxExcess {
|
||
excess = maxExcess
|
||
}
|
||
return 1 - excess/maxExcess
|
||
}
|
||
|
||
func latencyPreference(ms int) float64 {
|
||
if ms <= 0 {
|
||
return 1
|
||
}
|
||
return 1 / (1 + float64(ms)/100)
|
||
}
|
||
|
||
func laneSuccessRates(stats []LaneSuccessStat, requestedLane string) map[string]float64 {
|
||
type bucket struct {
|
||
total int
|
||
lane int
|
||
}
|
||
bySubnet := make(map[string]*bucket)
|
||
for _, s := range stats {
|
||
sub := NormalizeSubnet(s.Subnet)
|
||
if sub == "" || s.Success <= 0 {
|
||
continue
|
||
}
|
||
b := bySubnet[sub]
|
||
if b == nil {
|
||
b = &bucket{}
|
||
bySubnet[sub] = b
|
||
}
|
||
b.total += s.Success
|
||
if requestedLane != "" && strings.EqualFold(s.JoinLane, requestedLane) {
|
||
b.lane += s.Success
|
||
}
|
||
}
|
||
out := make(map[string]float64)
|
||
for sub, b := range bySubnet {
|
||
if b.total <= 0 {
|
||
continue
|
||
}
|
||
out[sub+"|"] = clamp01(float64(b.total) / float64(b.total+3))
|
||
if requestedLane != "" && b.lane > 0 {
|
||
out[sub+"|"+requestedLane] = clamp01(float64(b.lane) / float64(b.total))
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
func clamp01(v float64) float64 {
|
||
if v < 0 {
|
||
return 0
|
||
}
|
||
if v > 1 {
|
||
return 1
|
||
}
|
||
return v
|
||
}
|
||
|
||
func firstNonEmpty(parts ...string) string {
|
||
for _, p := range parts {
|
||
if strings.TrimSpace(p) != "" {
|
||
return strings.TrimSpace(p)
|
||
}
|
||
}
|
||
return ""
|
||
}
|
||
|
||
// NormalizeSubnet returns a /24-style prefix for routing keys.
|
||
func NormalizeSubnet(s string) string {
|
||
s = strings.TrimSpace(strings.ToLower(s))
|
||
if s == "" {
|
||
return ""
|
||
}
|
||
if strings.HasSuffix(s, ".x") {
|
||
return strings.TrimSuffix(s, ".x")
|
||
}
|
||
return SubnetFromIP(s)
|
||
}
|
||
|
||
// SubnetFromIP extracts a routable subnet prefix from an IP or CIDR-ish string.
|
||
func SubnetFromIP(ip string) string {
|
||
ip = strings.TrimSpace(ip)
|
||
if ip == "" {
|
||
return ""
|
||
}
|
||
host := ip
|
||
if h, _, err := net.SplitHostPort(ip); err == nil {
|
||
host = h
|
||
}
|
||
if strings.Count(host, ".") == 2 {
|
||
return host
|
||
}
|
||
parsed := net.ParseIP(host)
|
||
if parsed == nil {
|
||
return ""
|
||
}
|
||
if v4 := parsed.To4(); v4 != nil {
|
||
parts := strings.Split(host, ".")
|
||
if len(parts) >= 3 {
|
||
return strings.Join(parts[:3], ".")
|
||
}
|
||
}
|
||
if strings.Contains(host, ":") {
|
||
parts := strings.Split(host, ":")
|
||
if len(parts) >= 3 {
|
||
return strings.Join(parts[:3], ":")
|
||
}
|
||
}
|
||
return ""
|
||
}
|