Files
explorer-monorepo/backend/api/track1/bridge_status_data.go

277 lines
8.1 KiB
Go

package track1
import (
"context"
"os"
"strings"
"time"
"github.com/explorer/backend/api/freshness"
)
func relaySnapshotStatus(relay map[string]interface{}) string {
if relay == nil {
return ""
}
if probe, ok := relay["url_probe"].(map[string]interface{}); ok {
if okValue, exists := probe["ok"].(bool); exists && !okValue {
return "down"
}
if body, ok := probe["body"].(map[string]interface{}); ok {
if status, ok := body["status"].(string); ok {
return strings.ToLower(strings.TrimSpace(status))
}
}
}
if _, ok := relay["file_snapshot_error"].(string); ok {
return "down"
}
if snapshot, ok := relay["file_snapshot"].(map[string]interface{}); ok {
if status, ok := snapshot["status"].(string); ok {
return strings.ToLower(strings.TrimSpace(status))
}
}
return ""
}
func relayNeedsAttention(relay map[string]interface{}) bool {
status := relaySnapshotStatus(relay)
switch status {
case "degraded", "stale", "stopped", "down":
return true
default:
return false
}
}
// BuildBridgeStatusData builds the inner `data` object for bridge/status and SSE payloads.
func (s *Server) BuildBridgeStatusData(ctx context.Context) map[string]interface{} {
rpc138 := strings.TrimSpace(os.Getenv("RPC_URL"))
if rpc138 == "" {
rpc138 = "http://localhost:8545"
}
var probes []RPCProbeResult
p138 := ProbeEVMJSONRPC(ctx, "chain-138", "138", rpc138)
probes = append(probes, p138)
if eth := strings.TrimSpace(os.Getenv("ETH_MAINNET_RPC_URL")); eth != "" {
probes = append(probes, ProbeEVMJSONRPC(ctx, "ethereum-mainnet", "1", eth))
}
for _, row := range ParseExtraRPCProbes() {
name, u, ck := row[0], row[1], row[2]
probes = append(probes, ProbeEVMJSONRPC(ctx, name, ck, u))
}
overall := "operational"
if !p138.OK {
overall = "degraded"
} else {
for _, p := range probes {
if !p.OK {
overall = "degraded"
break
}
}
}
now := time.Now().UTC().Format(time.RFC3339)
chains := map[string]interface{}{
"138": map[string]interface{}{
"name": "Defi Oracle Meta Mainnet",
"status": chainStatusFromProbe(p138),
"last_sync": now,
"latency_ms": p138.LatencyMs,
"head_age_sec": p138.HeadAgeSeconds,
"block_number": p138.BlockNumberDec,
"endpoint": p138.Endpoint,
"probe_error": p138.Error,
},
}
for _, p := range probes {
if p.ChainKey != "1" && p.Name != "ethereum-mainnet" {
continue
}
chains["1"] = map[string]interface{}{
"name": "Ethereum Mainnet",
"status": chainStatusFromProbe(p),
"last_sync": now,
"latency_ms": p.LatencyMs,
"head_age_sec": p.HeadAgeSeconds,
"block_number": p.BlockNumberDec,
"endpoint": p.Endpoint,
"probe_error": p.Error,
}
break
}
probeJSON := make([]map[string]interface{}, 0, len(probes))
for _, p := range probes {
probeJSON = append(probeJSON, map[string]interface{}{
"name": p.Name,
"chainKey": p.ChainKey,
"endpoint": p.Endpoint,
"ok": p.OK,
"latencyMs": p.LatencyMs,
"blockNumber": p.BlockNumber,
"blockNumberDec": p.BlockNumberDec,
"headAgeSeconds": p.HeadAgeSeconds,
"error": p.Error,
})
}
data := map[string]interface{}{
"status": overall,
"chains": chains,
"rpc_probe": probeJSON,
"checked_at": now,
}
if ov := readOptionalVerifyJSON(); ov != nil {
data["operator_verify"] = ov
}
if s.freshnessLoader != nil {
if snapshot, completeness, sampling, err := s.freshnessLoader(ctx); err == nil && snapshot != nil {
subsystems := map[string]interface{}{
"rpc_head": map[string]interface{}{
"status": chainStatusFromProbe(p138),
"updated_at": valueOrNil(snapshot.ChainHead.Timestamp),
"age_seconds": valueOrNil(snapshot.ChainHead.AgeSeconds),
"source": snapshot.ChainHead.Source,
"confidence": snapshot.ChainHead.Confidence,
"provenance": snapshot.ChainHead.Provenance,
"completeness": snapshot.ChainHead.Completeness,
},
"tx_index": map[string]interface{}{
"status": completenessStatus(completeness.TransactionsFeed),
"updated_at": valueOrNil(snapshot.LatestIndexedTransaction.Timestamp),
"age_seconds": valueOrNil(snapshot.LatestIndexedTransaction.AgeSeconds),
"source": snapshot.LatestIndexedTransaction.Source,
"confidence": snapshot.LatestIndexedTransaction.Confidence,
"provenance": snapshot.LatestIndexedTransaction.Provenance,
"completeness": completeness.TransactionsFeed,
},
"stats_summary": map[string]interface{}{
"status": completenessStatus(completeness.BlocksFeed),
"updated_at": valueOrNil(sampling.StatsGeneratedAt),
"age_seconds": ageSinceRFC3339(sampling.StatsGeneratedAt),
"source": freshness.SourceReported,
"confidence": freshness.ConfidenceMedium,
"provenance": freshness.ProvenanceComposite,
"completeness": completeness.BlocksFeed,
},
}
if len(sampling.Issues) > 0 {
subsystems["freshness_queries"] = map[string]interface{}{
"status": "degraded",
"updated_at": valueOrNil(sampling.StatsGeneratedAt),
"age_seconds": ageSinceRFC3339(sampling.StatsGeneratedAt),
"source": freshness.SourceDerived,
"confidence": freshness.ConfidenceMedium,
"provenance": freshness.ProvenanceComposite,
"completeness": freshness.CompletenessPartial,
"issues": sampling.Issues,
}
}
modeKind := "live"
modeReason := any(nil)
modeScope := any(nil)
if relays, ok := data["ccip_relays"].(map[string]interface{}); ok && len(relays) > 0 {
modeKind = "snapshot"
modeReason = "live_homepage_stream_not_attached"
modeScope = "relay_monitoring_homepage_card_only"
subsystems["bridge_relay_monitoring"] = map[string]interface{}{
"status": overall,
"updated_at": now,
"age_seconds": int64(0),
"source": freshness.SourceReported,
"confidence": freshness.ConfidenceHigh,
"provenance": freshness.ProvenanceMissionFeed,
"completeness": freshness.CompletenessComplete,
}
}
data["freshness"] = snapshot
data["subsystems"] = subsystems
data["sampling"] = sampling
data["mode"] = map[string]interface{}{
"kind": modeKind,
"updated_at": now,
"age_seconds": int64(0),
"reason": modeReason,
"scope": modeScope,
"source": freshness.SourceReported,
"confidence": freshness.ConfidenceHigh,
"provenance": freshness.ProvenanceMissionFeed,
}
}
}
if relays := FetchCCIPRelayHealths(ctx); relays != nil {
data["ccip_relays"] = relays
if ccip := primaryRelayHealth(relays); ccip != nil {
data["ccip_relay"] = ccip
}
for _, value := range relays {
relay, ok := value.(map[string]interface{})
if ok && relayNeedsAttention(relay) {
data["status"] = "degraded"
break
}
}
}
if mode, ok := data["mode"].(map[string]interface{}); ok {
if relays, ok := data["ccip_relays"].(map[string]interface{}); ok && len(relays) > 0 {
mode["kind"] = "snapshot"
mode["reason"] = "live_homepage_stream_not_attached"
mode["scope"] = "relay_monitoring_homepage_card_only"
if subsystems, ok := data["subsystems"].(map[string]interface{}); ok {
subsystems["bridge_relay_monitoring"] = map[string]interface{}{
"status": data["status"],
"updated_at": now,
"age_seconds": int64(0),
"source": freshness.SourceReported,
"confidence": freshness.ConfidenceHigh,
"provenance": freshness.ProvenanceMissionFeed,
"completeness": freshness.CompletenessComplete,
}
}
}
}
return data
}
func valueOrNil[T any](value *T) any {
if value == nil {
return nil
}
return *value
}
func ageSinceRFC3339(value *string) any {
if value == nil || *value == "" {
return nil
}
parsed, err := time.Parse(time.RFC3339, *value)
if err != nil {
return nil
}
age := int64(time.Since(parsed).Seconds())
if age < 0 {
age = 0
}
return age
}
func completenessStatus(value freshness.Completeness) string {
switch value {
case freshness.CompletenessComplete:
return "operational"
case freshness.CompletenessPartial:
return "partial"
case freshness.CompletenessStale:
return "stale"
default:
return "unavailable"
}
}