Files
explorer-monorepo/backend/api/track1/ccip_health_test.go
defiQUG 0c869f7930 feat(freshness): enhance diagnostics and update snapshot structure
- Introduced a new Diagnostics struct to capture transaction visibility state and activity state.
- Updated BuildSnapshot function to return diagnostics alongside snapshot, completeness, and sampling.
- Enhanced test cases to validate the new diagnostics data.
- Updated frontend components to utilize the new diagnostics information for improved user feedback on freshness context.

This change improves the observability of transaction activity and enhances the user experience by providing clearer insights into the freshness of data.
2026-04-12 18:22:08 -07:00

264 lines
9.4 KiB
Go

package track1
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strconv"
"testing"
"time"
"github.com/explorer/backend/api/freshness"
"github.com/stretchr/testify/require"
)
func TestFetchCCIPRelayHealthFromURL(t *testing.T) {
relay := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"ok":true,"status":"operational","destination":{"chain_name":"Ethereum Mainnet"},"queue":{"size":0}}`))
}))
defer relay.Close()
t.Setenv("CCIP_RELAY_HEALTH_URL", relay.URL+"/healthz")
t.Setenv("CCIP_RELAY_HEALTH_URLS", "")
t.Setenv("MISSION_CONTROL_CCIP_JSON", "")
got := FetchCCIPRelayHealth(context.Background())
require.NotNil(t, got)
probe, ok := got["url_probe"].(map[string]interface{})
require.True(t, ok)
require.Equal(t, true, probe["ok"])
body, ok := probe["body"].(map[string]interface{})
require.True(t, ok)
require.Equal(t, "operational", body["status"])
dest, ok := body["destination"].(map[string]interface{})
require.True(t, ok)
require.Equal(t, "Ethereum Mainnet", dest["chain_name"])
}
func TestFetchCCIPRelayHealthsFromNamedURLs(t *testing.T) {
mainnet := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"status":"operational","destination":{"chain_name":"Ethereum Mainnet"},"queue":{"size":0}}`))
}))
defer mainnet.Close()
bsc := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"status":"operational","destination":{"chain_name":"BSC"},"queue":{"size":1}}`))
}))
defer bsc.Close()
t.Setenv("CCIP_RELAY_HEALTH_URL", "")
t.Setenv("MISSION_CONTROL_CCIP_JSON", "")
t.Setenv("CCIP_RELAY_HEALTH_URLS", "mainnet="+mainnet.URL+"/healthz,bsc="+bsc.URL+"/healthz")
got := FetchCCIPRelayHealths(context.Background())
require.NotNil(t, got)
mainnetRelay, ok := got["mainnet"].(map[string]interface{})
require.True(t, ok)
mainnetProbe, ok := mainnetRelay["url_probe"].(map[string]interface{})
require.True(t, ok)
require.Equal(t, true, mainnetProbe["ok"])
bscRelay, ok := got["bsc"].(map[string]interface{})
require.True(t, ok)
bscProbe, ok := bscRelay["url_probe"].(map[string]interface{})
require.True(t, ok)
body, ok := bscProbe["body"].(map[string]interface{})
require.True(t, ok)
dest, ok := body["destination"].(map[string]interface{})
require.True(t, ok)
require.Equal(t, "BSC", dest["chain_name"])
}
func TestFetchCCIPRelayHealthPrefersMainnetCW(t *testing.T) {
relays := map[string]interface{}{
"mainnet_weth": map[string]interface{}{"url_probe": map[string]interface{}{"ok": true}},
"mainnet_cw": map[string]interface{}{"url_probe": map[string]interface{}{"ok": true, "body": map[string]interface{}{"status": "operational"}}},
"bsc": map[string]interface{}{"url_probe": map[string]interface{}{"ok": true}},
}
got := primaryRelayHealth(relays)
require.NotNil(t, got)
require.Equal(t, relays["mainnet_cw"], got)
}
func TestFetchCCIPRelayHealthFromFileSnapshot(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "relay-health.json")
require.NoError(t, os.WriteFile(path, []byte(`{"status":"paused","queue":{"size":3}}`), 0o644))
t.Setenv("CCIP_RELAY_HEALTH_URL", "")
t.Setenv("CCIP_RELAY_HEALTH_URLS", "")
t.Setenv("MISSION_CONTROL_CCIP_JSON", path)
got := FetchCCIPRelayHealth(context.Background())
require.NotNil(t, got)
snapshot, ok := got["file_snapshot"].(map[string]interface{})
require.True(t, ok)
require.Equal(t, "paused", snapshot["status"])
queue, ok := snapshot["queue"].(map[string]interface{})
require.True(t, ok)
require.Equal(t, float64(3), queue["size"])
}
func TestBuildBridgeStatusDataIncludesCCIPRelay(t *testing.T) {
rpc := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var req struct {
Method string `json:"method"`
}
require.NoError(t, json.NewDecoder(r.Body).Decode(&req))
w.Header().Set("Content-Type", "application/json")
switch req.Method {
case "eth_blockNumber":
_, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"0x10"}`))
case "eth_getBlockByNumber":
ts := strconv.FormatInt(time.Now().Add(-2*time.Second).Unix(), 16)
_, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":{"timestamp":"0x` + ts + `"}}`))
default:
http.Error(w, `{"jsonrpc":"2.0","id":1,"error":{"message":"unsupported"}}`, http.StatusBadRequest)
}
}))
defer rpc.Close()
relay := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"ok":true,"status":"operational","queue":{"size":0}}`))
}))
defer relay.Close()
t.Setenv("RPC_URL", rpc.URL)
t.Setenv("ETH_MAINNET_RPC_URL", "")
t.Setenv("MISSION_CONTROL_EXTRA_RPCS", "")
t.Setenv("MISSION_CONTROL_VERIFY_JSON", "")
t.Setenv("CCIP_RELAY_HEALTH_URL", relay.URL+"/healthz")
t.Setenv("CCIP_RELAY_HEALTH_URLS", "")
t.Setenv("MISSION_CONTROL_CCIP_JSON", "")
s := &Server{
freshnessLoader: func(context.Context) (*freshness.Snapshot, *freshness.SummaryCompleteness, *freshness.Sampling, *freshness.Diagnostics, 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},
&freshness.Diagnostics{
TxVisibilityState: "lagging",
ActivityState: "fresh_head_stale_transaction_visibility",
Source: freshness.SourceReported,
Confidence: freshness.ConfidenceMedium,
Provenance: freshness.ProvenanceComposite,
Completeness: freshness.CompletenessPartial,
},
nil
},
}
got := s.BuildBridgeStatusData(context.Background())
ccip, ok := got["ccip_relay"].(map[string]interface{})
require.True(t, ok)
relays, ok := got["ccip_relays"].(map[string]interface{})
require.True(t, ok)
require.Contains(t, relays, "mainnet")
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, "diagnostics")
require.Contains(t, got, "subsystems")
require.Contains(t, got, "mode")
}
func TestBuildBridgeStatusDataDegradesWhenNamedRelayFails(t *testing.T) {
rpc := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var req struct {
Method string `json:"method"`
}
require.NoError(t, json.NewDecoder(r.Body).Decode(&req))
w.Header().Set("Content-Type", "application/json")
switch req.Method {
case "eth_blockNumber":
_, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"0x10"}`))
case "eth_getBlockByNumber":
ts := strconv.FormatInt(time.Now().Add(-2*time.Second).Unix(), 16)
_, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":{"timestamp":"0x` + ts + `"}}`))
default:
http.Error(w, `{"jsonrpc":"2.0","id":1,"error":{"message":"unsupported"}}`, http.StatusBadRequest)
}
}))
defer rpc.Close()
mainnet := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"status":"operational","queue":{"size":0}}`))
}))
defer mainnet.Close()
bad := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, `{"status":"degraded"}`, http.StatusBadGateway)
}))
defer bad.Close()
t.Setenv("RPC_URL", rpc.URL)
t.Setenv("ETH_MAINNET_RPC_URL", "")
t.Setenv("MISSION_CONTROL_EXTRA_RPCS", "")
t.Setenv("MISSION_CONTROL_VERIFY_JSON", "")
t.Setenv("CCIP_RELAY_HEALTH_URL", "")
t.Setenv("MISSION_CONTROL_CCIP_JSON", "")
t.Setenv("CCIP_RELAY_HEALTH_URLS", "mainnet="+mainnet.URL+"/healthz,bsc="+bad.URL+"/healthz")
s := &Server{
freshnessLoader: func(context.Context) (*freshness.Snapshot, *freshness.SummaryCompleteness, *freshness.Sampling, *freshness.Diagnostics, error) {
return nil, nil, nil, nil, nil
},
}
got := s.BuildBridgeStatusData(context.Background())
require.Equal(t, "degraded", got["status"])
}