- Backend REST/gateway/track routes, analytics, Blockscout proxy paths. - Frontend wallet and liquidity surfaces; MetaMask token list alignment. - Deployment docs, verification scripts, address inventory updates. Check: go build ./... under backend/ (pass). Made-with: Cursor
183 lines
4.6 KiB
Go
183 lines
4.6 KiB
Go
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))
|
|
}
|