- 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.
264 lines
9.4 KiB
Go
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"])
|
|
}
|