package track1 import ( "context" "encoding/json" "io" "net/http" "os" "sort" "strconv" "strings" "time" ) type relayHealthTarget struct { Name string URL string } func fetchRelayHealthURL(ctx context.Context, u string) map[string]interface{} { out := make(map[string]interface{}) c := &http.Client{Timeout: 4 * time.Second} req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) if err != nil { out["url_probe"] = map[string]interface{}{"ok": false, "error": err.Error()} } else { resp, err := c.Do(req) if err != nil { out["url_probe"] = map[string]interface{}{"ok": false, "error": err.Error()} } else { func() { defer resp.Body.Close() b, _ := io.ReadAll(io.LimitReader(resp.Body, 256*1024)) ok := resp.StatusCode >= 200 && resp.StatusCode < 300 var j interface{} if json.Unmarshal(b, &j) == nil { out["url_probe"] = map[string]interface{}{"ok": ok, "status": resp.StatusCode, "body": j} } else { out["url_probe"] = map[string]interface{}{"ok": ok, "status": resp.StatusCode, "raw": string(b)} } }() } } return out } func fetchRelayHealthFileSnapshot(p string) map[string]interface{} { out := make(map[string]interface{}) if p != "" { b, err := os.ReadFile(p) if err != nil { out["file_snapshot_error"] = err.Error() } else if len(b) > 512*1024 { out["file_snapshot_error"] = "file too large" } else { var j interface{} if err := json.Unmarshal(b, &j); err != nil { out["file_snapshot_error"] = err.Error() } else { out["file_snapshot"] = j } } } return out } func buildRelayHealthSignal(ctx context.Context, url, filePath string) map[string]interface{} { out := make(map[string]interface{}) if strings.TrimSpace(url) != "" { for key, value := range fetchRelayHealthURL(ctx, url) { out[key] = value } } if strings.TrimSpace(filePath) != "" { for key, value := range fetchRelayHealthFileSnapshot(filePath) { out[key] = value } } if len(out) == 0 { return nil } return out } func normalizeRelayHealthName(raw string, index int) string { name := strings.TrimSpace(strings.ToLower(raw)) if name == "" { return "relay_" + strconv.Itoa(index) } replacer := strings.NewReplacer(" ", "_", "-", "_", "/", "_") name = replacer.Replace(name) return name } func parseRelayHealthTargets() []relayHealthTarget { raw := strings.TrimSpace(os.Getenv("CCIP_RELAY_HEALTH_URLS")) if raw == "" { return nil } normalized := strings.NewReplacer("\n", ",", ";", ",").Replace(raw) parts := strings.Split(normalized, ",") targets := make([]relayHealthTarget, 0, len(parts)) for idx, part := range parts { part = strings.TrimSpace(part) if part == "" { continue } name := "" url := part if strings.Contains(part, "=") { chunks := strings.SplitN(part, "=", 2) name = normalizeRelayHealthName(chunks[0], idx+1) url = strings.TrimSpace(chunks[1]) } else { name = normalizeRelayHealthName("", idx+1) } if url == "" { continue } targets = append(targets, relayHealthTarget{Name: name, URL: url}) } return targets } // FetchCCIPRelayHealths returns optional named CCIP / relay signals from URL probes and/or operator JSON files. // Safe defaults: short timeouts, small body cap. Omit from payload when nothing is configured. func FetchCCIPRelayHealths(ctx context.Context) map[string]interface{} { relays := make(map[string]interface{}) if legacy := buildRelayHealthSignal( ctx, strings.TrimSpace(os.Getenv("CCIP_RELAY_HEALTH_URL")), strings.TrimSpace(os.Getenv("MISSION_CONTROL_CCIP_JSON")), ); legacy != nil { relays["mainnet"] = legacy } for _, target := range parseRelayHealthTargets() { if _, exists := relays[target.Name]; exists { continue } if relay := buildRelayHealthSignal(ctx, target.URL, ""); relay != nil { relays[target.Name] = relay } } if len(relays) == 0 { return nil } return relays } func primaryRelayHealth(relays map[string]interface{}) map[string]interface{} { if len(relays) == 0 { return nil } preferred := []string{"mainnet_cw", "mainnet_weth", "mainnet"} for _, key := range preferred { if relay, ok := relays[key].(map[string]interface{}); ok { return relay } } keys := make([]string, 0, len(relays)) for key := range relays { keys = append(keys, key) } sort.Strings(keys) for _, key := range keys { if relay, ok := relays[key].(map[string]interface{}); ok { return relay } } return nil } // FetchCCIPRelayHealth returns the primary relay signal for legacy callers. func FetchCCIPRelayHealth(ctx context.Context) map[string]interface{} { return primaryRelayHealth(FetchCCIPRelayHealths(ctx)) }