mirror of
https://github.com/GMWalletApp/epusdt.git
synced 2026-07-07 18:26:16 +00:00
feat: add in-memory RPC runtime stats SSE endpoint
This commit is contained in:
@@ -325,7 +325,15 @@ func resolveSolanaRpcNode(excludeIDs ...uint64) (*mdb.RpcNode, error) {
|
||||
}
|
||||
|
||||
// SolRetryClient 发送 Solana JSON-RPC 请求,自动重试
|
||||
func SolRetryClient(method string, params []interface{}) ([]byte, error) {
|
||||
func SolRetryClient(method string, params []interface{}) (body []byte, err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
data.RecordRpcFailure(mdb.NetworkSolana)
|
||||
return
|
||||
}
|
||||
data.RecordRpcSuccess(mdb.NetworkSolana)
|
||||
}()
|
||||
|
||||
tried := make([]uint64, 0, 3)
|
||||
var lastErr error
|
||||
for attempts := 0; attempts < 3; attempts++ {
|
||||
@@ -443,6 +451,32 @@ func SolGetSignaturesForAddress(address string, limit int, untilSig string, befo
|
||||
return bodyData, nil
|
||||
}
|
||||
|
||||
func SolGetBlockHeight() (height int64, err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
data.RecordRpcFailure(mdb.NetworkSolana)
|
||||
return
|
||||
}
|
||||
data.RecordRpcSuccess(mdb.NetworkSolana)
|
||||
}()
|
||||
|
||||
rpcURL, err := resolveSolanaRpcURL()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
bodyData, err := solRetryClientWithURL(rpcURL, "getBlockHeight", []interface{}{
|
||||
map[string]interface{}{"commitment": "finalized"},
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
height = gjson.GetBytes(bodyData, "result").Int()
|
||||
if height <= 0 {
|
||||
return 0, fmt.Errorf("unexpected getBlockHeight response: %s", string(bodyData))
|
||||
}
|
||||
return height, nil
|
||||
}
|
||||
|
||||
func SolGetTransaction(sig string) ([]byte, error) {
|
||||
txData, err := SolRetryClient("getTransaction", []interface{}{
|
||||
sig,
|
||||
|
||||
@@ -102,6 +102,53 @@ func TestResolveSolanaRpcURLUsesGeneralWhenManualVerifyExists(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSolGetBlockHeightDoesNotAffectRpcNodeFailover(t *testing.T) {
|
||||
cleanup := testutil.SetupTestDatabases(t)
|
||||
defer cleanup()
|
||||
data.ResetRpcFailoverForTest()
|
||||
data.ResetRpcRuntimeStatsForTest()
|
||||
t.Cleanup(data.ResetRpcFailoverForTest)
|
||||
t.Cleanup(data.ResetRpcRuntimeStatsForTest)
|
||||
|
||||
oldRetryCount := solRPCRetryCount
|
||||
solRPCRetryCount = 0
|
||||
t.Cleanup(func() {
|
||||
solRPCRetryCount = oldRetryCount
|
||||
})
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "temporary", http.StatusBadGateway)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
node := &mdb.RpcNode{
|
||||
Network: mdb.NetworkSolana,
|
||||
Url: server.URL,
|
||||
Type: mdb.RpcNodeTypeHttp,
|
||||
Weight: 1,
|
||||
Enabled: true,
|
||||
Purpose: mdb.RpcNodePurposeGeneral,
|
||||
Status: mdb.RpcNodeStatusOk,
|
||||
}
|
||||
if err := dao.Mdb.Create(node).Error; err != nil {
|
||||
t.Fatalf("seed solana rpc_node: %v", err)
|
||||
}
|
||||
|
||||
for i := 0; i < data.RpcFailoverThreshold+1; i++ {
|
||||
if _, err := SolGetBlockHeight(); err == nil {
|
||||
t.Fatalf("SolGetBlockHeight attempt %d unexpectedly succeeded", i+1)
|
||||
}
|
||||
}
|
||||
|
||||
if data.IsRpcNodeCoolingDown(node.ID) {
|
||||
t.Fatal("SolGetBlockHeight failures should not affect rpc_node failover cooldown")
|
||||
}
|
||||
stats := data.SnapshotRpcRuntimeStats()
|
||||
if got := stats[mdb.NetworkSolana].FailureCount; got != int64(data.RpcFailoverThreshold+1) {
|
||||
t.Fatalf("solana runtime failure_count = %d, want %d", got, data.RpcFailoverThreshold+1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSolRetryClientSwitchesAfterFailureThreshold(t *testing.T) {
|
||||
cleanup := testutil.SetupTestDatabases(t)
|
||||
defer cleanup()
|
||||
|
||||
Reference in New Issue
Block a user