Freshness diagnostics API, UI trust notes, mission control/stats updates, and deploy scripts.

Made-with: Cursor
This commit is contained in:
defiQUG
2026-04-12 06:33:54 -07:00
parent 0972178cc5
commit 3fdb812a29
63 changed files with 5163 additions and 826 deletions

View File

@@ -5,6 +5,8 @@ import (
"os"
"strings"
"time"
"github.com/explorer/backend/api/freshness"
)
func relaySnapshotStatus(relay map[string]interface{}) string {
@@ -129,6 +131,81 @@ func (s *Server) BuildBridgeStatusData(ctx context.Context) map[string]interface
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 {
@@ -142,5 +219,58 @@ func (s *Server) BuildBridgeStatusData(ctx context.Context) map[string]interface
}
}
}
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"
}
}

View File

@@ -11,6 +11,7 @@ import (
"testing"
"time"
"github.com/explorer/backend/api/freshness"
"github.com/stretchr/testify/require"
)
@@ -145,7 +146,50 @@ func TestBuildBridgeStatusDataIncludesCCIPRelay(t *testing.T) {
t.Setenv("CCIP_RELAY_HEALTH_URLS", "")
t.Setenv("MISSION_CONTROL_CCIP_JSON", "")
s := &Server{}
s := &Server{
freshnessLoader: func(context.Context) (*freshness.Snapshot, *freshness.SummaryCompleteness, *freshness.Sampling, error) {
now := time.Now().UTC().Format(time.RFC3339)
head := int64(16)
txBlock := int64(12)
distance := int64(4)
return &freshness.Snapshot{
ChainHead: freshness.Reference{
BlockNumber: &head,
Timestamp: &now,
AgeSeconds: func() *int64 { v := int64(1); return &v }(),
Source: freshness.SourceReported,
Confidence: freshness.ConfidenceHigh,
Provenance: freshness.ProvenanceRPC,
Completeness: freshness.CompletenessComplete,
},
LatestIndexedTransaction: freshness.Reference{
BlockNumber: &txBlock,
Timestamp: &now,
AgeSeconds: func() *int64 { v := int64(120); return &v }(),
Source: freshness.SourceReported,
Confidence: freshness.ConfidenceHigh,
Provenance: freshness.ProvenanceTxIndex,
Completeness: freshness.CompletenessPartial,
},
LatestNonEmptyBlock: freshness.Reference{
BlockNumber: &txBlock,
Timestamp: &now,
AgeSeconds: func() *int64 { v := int64(120); return &v }(),
DistanceFromHead: &distance,
Source: freshness.SourceReported,
Confidence: freshness.ConfidenceHigh,
Provenance: freshness.ProvenanceTxIndex,
Completeness: freshness.CompletenessPartial,
},
},
&freshness.SummaryCompleteness{
TransactionsFeed: freshness.CompletenessPartial,
BlocksFeed: freshness.CompletenessComplete,
},
&freshness.Sampling{StatsGeneratedAt: &now},
nil
},
}
got := s.BuildBridgeStatusData(context.Background())
ccip, ok := got["ccip_relay"].(map[string]interface{})
require.True(t, ok)
@@ -156,6 +200,9 @@ func TestBuildBridgeStatusDataIncludesCCIPRelay(t *testing.T) {
probe, ok := ccip["url_probe"].(map[string]interface{})
require.True(t, ok)
require.Equal(t, true, probe["ok"])
require.Contains(t, got, "freshness")
require.Contains(t, got, "subsystems")
require.Contains(t, got, "mode")
}
func TestBuildBridgeStatusDataDegradesWhenNamedRelayFails(t *testing.T) {
@@ -197,7 +244,11 @@ func TestBuildBridgeStatusDataDegradesWhenNamedRelayFails(t *testing.T) {
t.Setenv("MISSION_CONTROL_CCIP_JSON", "")
t.Setenv("CCIP_RELAY_HEALTH_URLS", "mainnet="+mainnet.URL+"/healthz,bsc="+bad.URL+"/healthz")
s := &Server{}
s := &Server{
freshnessLoader: func(context.Context) (*freshness.Snapshot, *freshness.SummaryCompleteness, *freshness.Sampling, error) {
return nil, nil, nil, nil
},
}
got := s.BuildBridgeStatusData(context.Background())
require.Equal(t, "degraded", got["status"])
}

View File

@@ -12,6 +12,7 @@ import (
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/explorer/backend/api/freshness"
"github.com/explorer/backend/libs/go-rpc-gateway"
)
@@ -19,13 +20,18 @@ var track1HashPattern = regexp.MustCompile(`^0x[a-fA-F0-9]{64}$`)
// Server handles Track 1 endpoints (uses RPC gateway from lib)
type Server struct {
rpcGateway *gateway.RPCGateway
rpcGateway *gateway.RPCGateway
freshnessLoader func(ctx context.Context) (*freshness.Snapshot, *freshness.SummaryCompleteness, *freshness.Sampling, error)
}
// NewServer creates a new Track 1 server
func NewServer(rpcGateway *gateway.RPCGateway) *Server {
func NewServer(
rpcGateway *gateway.RPCGateway,
freshnessLoader func(ctx context.Context) (*freshness.Snapshot, *freshness.SummaryCompleteness, *freshness.Sampling, error),
) *Server {
return &Server{
rpcGateway: rpcGateway,
rpcGateway: rpcGateway,
freshnessLoader: freshnessLoader,
}
}