134 lines
3.2 KiB
Go
134 lines
3.2 KiB
Go
package monitor
|
|
|
|
import (
|
|
"context"
|
|
"math"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gogenex/bridge-monitor/internal/eth"
|
|
"github.com/gogenex/bridge-monitor/internal/genex"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// ReconciliationResult 对账结果
|
|
type ReconciliationResult struct {
|
|
EthLocked float64 `json:"ethLocked"`
|
|
GenexMinted float64 `json:"genexMinted"`
|
|
Discrepancy float64 `json:"discrepancy"`
|
|
Healthy bool `json:"healthy"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
BlockEth int64 `json:"blockEth"`
|
|
BlockGenex int64 `json:"blockGenex"`
|
|
}
|
|
|
|
// BridgeMonitor 跨链桥监控器
|
|
type BridgeMonitor struct {
|
|
ethClient *eth.Client
|
|
genexClient *genex.Client
|
|
alerter *Alerter
|
|
metrics *Metrics
|
|
threshold float64
|
|
|
|
mu sync.RWMutex
|
|
latest *ReconciliationResult
|
|
history []ReconciliationResult
|
|
}
|
|
|
|
func NewBridgeMonitor(
|
|
ethClient *eth.Client,
|
|
genexClient *genex.Client,
|
|
alerter *Alerter,
|
|
metrics *Metrics,
|
|
threshold float64,
|
|
) *BridgeMonitor {
|
|
return &BridgeMonitor{
|
|
ethClient: ethClient,
|
|
genexClient: genexClient,
|
|
alerter: alerter,
|
|
metrics: metrics,
|
|
threshold: threshold,
|
|
history: make([]ReconciliationResult, 0, 1000),
|
|
}
|
|
}
|
|
|
|
// Reconcile 执行对账:两侧资产必须一致
|
|
func (bm *BridgeMonitor) Reconcile(ctx context.Context) (*ReconciliationResult, error) {
|
|
// Ethereum 侧:查询 Axelar Gateway 合约锁定的 USDC
|
|
ethLocked, ethBlock, err := bm.ethClient.GetLockedAmount("USDC")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Genex Chain 侧:查询桥铸造的 wrapped USDC 总量
|
|
genexMinted, genexBlock, err := bm.genexClient.GetBridgeTokenSupply("USDC")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
discrepancy := math.Abs(ethLocked - genexMinted)
|
|
|
|
result := ReconciliationResult{
|
|
EthLocked: ethLocked,
|
|
GenexMinted: genexMinted,
|
|
Discrepancy: discrepancy,
|
|
Healthy: true,
|
|
Timestamp: time.Now(),
|
|
BlockEth: ethBlock,
|
|
BlockGenex: genexBlock,
|
|
}
|
|
|
|
// 更新 Prometheus 指标
|
|
bm.metrics.SetEthLocked(ethLocked)
|
|
bm.metrics.SetGenexMinted(genexMinted)
|
|
bm.metrics.SetDiscrepancy(discrepancy)
|
|
bm.metrics.SetTVL(ethLocked)
|
|
bm.metrics.IncReconciliation()
|
|
|
|
// 偏差检查
|
|
if ethLocked > 0 && discrepancy > ethLocked*bm.threshold {
|
|
result.Healthy = false
|
|
log.WithField("ethLocked", ethLocked).
|
|
WithField("genexMinted", genexMinted).
|
|
WithField("discrepancy", discrepancy).
|
|
Error("Bridge asset discrepancy detected!")
|
|
|
|
bm.alerter.Critical("Bridge asset discrepancy detected", map[string]interface{}{
|
|
"ethLocked": ethLocked,
|
|
"genexMinted": genexMinted,
|
|
"discrepancy": discrepancy,
|
|
})
|
|
|
|
bm.alerter.EmergencyPause()
|
|
bm.metrics.IncEmergencyPause()
|
|
}
|
|
|
|
// 保存结果
|
|
bm.mu.Lock()
|
|
bm.latest = &result
|
|
bm.history = append(bm.history, result)
|
|
if len(bm.history) > 1000 {
|
|
bm.history = bm.history[len(bm.history)-1000:]
|
|
}
|
|
bm.mu.Unlock()
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// GetLatestResult 获取最新对账结果
|
|
func (bm *BridgeMonitor) GetLatestResult() *ReconciliationResult {
|
|
bm.mu.RLock()
|
|
defer bm.mu.RUnlock()
|
|
return bm.latest
|
|
}
|
|
|
|
// GetHistory 获取最近 N 条对账记录
|
|
func (bm *BridgeMonitor) GetHistory(n int) []ReconciliationResult {
|
|
bm.mu.RLock()
|
|
defer bm.mu.RUnlock()
|
|
if n > len(bm.history) {
|
|
n = len(bm.history)
|
|
}
|
|
return bm.history[len(bm.history)-n:]
|
|
}
|