Skip to content

Commit 3e643cf

Browse files
committed
feat: add GetMonitoringInfo in onchainmonitor
1 parent 884338e commit 3e643cf

1 file changed

Lines changed: 109 additions & 36 deletions

File tree

services/bridge/internal/leader/onchain_monitor.go

Lines changed: 109 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ type OnChainMonitor struct {
3737
cancel context.CancelFunc
3838
}
3939

40+
func (m *OnChainMonitor) IsEnabled() bool {
41+
return m.enabled
42+
}
43+
4044
// RouterMonitor tracks one router and its destinations
4145
type RouterMonitor struct {
4246
RouterID string
@@ -70,7 +74,7 @@ func DefaultMonitorConfig() MonitorConfig {
7074
return MonitorConfig{
7175
Enabled: false,
7276
TimeThresholdOffset: 1 * time.Minute,
73-
PriceDeviationOffset: big.NewFloat(0.10), // 10%
77+
PriceDeviationOffset: big.NewFloat(0.50), // 10%
7478
CheckInterval: 10 * time.Second,
7579
}
7680
}
@@ -218,7 +222,7 @@ func (m *OnChainMonitor) checkRouter(routerMonitor *RouterMonitor) {
218222
percentageChange.Mul(percentageChange, big.NewFloat(100))
219223
dest.lastPercentageChange = percentageChange
220224

221-
priceDevThreshold := m.GetPriceDeviationThreshold(dest.ChainID, dest.ContractAddress, symbol)
225+
priceDevThreshold := m.getPriceDeviationThresholdWithOffset(dest.ChainID, dest.ContractAddress, symbol)
222226
if priceDevThreshold != nil {
223227
thresholdPercent := new(big.Float).Mul(priceDevThreshold, big.NewFloat(100))
224228
absChange := new(big.Float).Abs(percentageChange)
@@ -259,6 +263,11 @@ func (m *OnChainMonitor) checkRouter(routerMonitor *RouterMonitor) {
259263
}
260264
}
261265

266+
// FindDestination
267+
func (m *OnChainMonitor) FindDestination(key string) *DestinationMonitor {
268+
return m.findDestination(key)
269+
}
270+
262271
func (m *OnChainMonitor) findDestination(key string) *DestinationMonitor {
263272
m.mu.RLock()
264273
defer m.mu.RUnlock()
@@ -274,6 +283,10 @@ func (m *OnChainMonitor) findDestination(key string) *DestinationMonitor {
274283
return nil
275284
}
276285

286+
func (m *OnChainMonitor) generateKey(chainID int64, contractAddress common.Address, symbol string) string {
287+
return utils.GenerateDestinationKey(chainID, contractAddress.Hex(), symbol)
288+
}
289+
277290
func (m *OnChainMonitor) getValueFromContract(dest *DestinationMonitor, symbol string) (*big.Int, uint64, error) {
278291

279292
const getValueABI = `[{
@@ -336,13 +349,12 @@ func formatValue(v *big.Int) string {
336349
return v.String()
337350
}
338351

339-
// ShouldProcess checks if a destination should be processed
340352
func (m *OnChainMonitor) ShouldProcess(chainID int64, contractAddress common.Address, symbol string, incomingPrice *big.Int) bool {
341353
if !m.enabled {
342354
return true
343355
}
344356

345-
key := utils.GenerateDestinationKey(chainID, contractAddress.Hex(), symbol)
357+
key := m.generateKey(chainID, contractAddress, symbol)
346358
dest := m.findDestination(key)
347359
if dest == nil {
348360
return true
@@ -365,45 +377,64 @@ func (m *OnChainMonitor) ShouldProcess(chainID int64, contractAddress common.Add
365377
return true
366378
}
367379

368-
if lastPercentageChange != nil {
369-
priceDevThreshold := m.GetPriceDeviationThreshold(chainID, contractAddress, symbol)
370-
if priceDevThreshold != nil {
371-
thresholdPercent := new(big.Float).Mul(priceDevThreshold, big.NewFloat(100))
372-
absChange := new(big.Float).Abs(lastPercentageChange)
373-
if absChange.Cmp(thresholdPercent) > 0 {
374-
logger.Infof("Monitoring triggered: price deviation threshold exceeded for chain=%d contract=%s symbol=%s, "+
375-
"change=%.2f%% threshold=%.2f%% value=%s",
376-
chainID, contractAddress.Hex(), symbol, lastPercentageChange, thresholdPercent, formatValue(currentValue))
377-
return true
380+
// skip deviation checks if last update was within last 2 minutes
381+
timeSinceUpdate := time.Since(time.Unix(int64(lastTimestamp), 0))
382+
skipDeviationCheck := timeSinceUpdate < 2*time.Minute
383+
384+
if skipDeviationCheck {
385+
logger.Debugf("Skipping deviation check for chain=%d contract=%s symbol=%s (last update %v ago < 2 minutes)",
386+
chainID, contractAddress.Hex(), symbol, timeSinceUpdate)
387+
} else {
388+
if lastPercentageChange != nil {
389+
priceDevThreshold := m.getPriceDeviationThresholdWithOffset(chainID, contractAddress, symbol)
390+
if priceDevThreshold != nil {
391+
thresholdPercent := new(big.Float).Mul(priceDevThreshold, big.NewFloat(100))
392+
absChange := new(big.Float).Abs(lastPercentageChange)
393+
if absChange.Cmp(thresholdPercent) > 0 {
394+
logger.Infof("Monitoring triggered: price deviation threshold exceeded for chain=%d contract=%s symbol=%s, "+
395+
"change=%.2f%% threshold=%.2f%% value=%s",
396+
chainID, contractAddress.Hex(), symbol, lastPercentageChange, thresholdPercent, formatValue(currentValue))
397+
return true
398+
}
378399
}
379400
}
380-
}
381401

382-
if incomingPrice != nil && currentValue != nil && currentValue.Sign() != 0 {
383-
priceDevThreshold := m.GetPriceDeviationThreshold(chainID, contractAddress, symbol)
384-
if priceDevThreshold != nil {
385-
diff := new(big.Int).Sub(incomingPrice, currentValue)
386-
oldFloat := new(big.Float).SetInt(currentValue)
387-
diffFloat := new(big.Float).SetInt(diff)
388-
percentageChange := new(big.Float).Quo(diffFloat, oldFloat)
389-
percentageChange.Mul(percentageChange, big.NewFloat(100))
390-
thresholdPercent := new(big.Float).Mul(priceDevThreshold, big.NewFloat(100))
391-
absChange := new(big.Float).Abs(percentageChange)
392-
if absChange.Cmp(thresholdPercent) > 0 {
393-
logger.Infof("Monitoring triggered: price deviation threshold exceeded (incoming vs on-chain) for chain=%d contract=%s symbol=%s, "+
394-
"change=%.2f%% threshold=%.2f%% onchain_value=%s incoming_value=%s",
395-
chainID, contractAddress.Hex(), symbol, percentageChange, thresholdPercent, formatValue(currentValue), incomingPrice.String())
396-
return true
402+
if incomingPrice != nil {
403+
freshValue, _, err := m.getValueFromContract(dest, symbol)
404+
if err != nil {
405+
logger.Warnf("Failed to fetch fresh on-chain value for chain=%d contract=%s symbol=%s, using cached value: %v",
406+
chainID, contractAddress.Hex(), symbol, err)
407+
// Fall back to cached value if fetch fails
408+
freshValue = currentValue
409+
}
410+
411+
if freshValue != nil && freshValue.Sign() != 0 {
412+
priceDevThreshold := m.getPriceDeviationThresholdWithOffset(chainID, contractAddress, symbol)
413+
if priceDevThreshold != nil {
414+
diff := new(big.Int).Sub(incomingPrice, freshValue)
415+
oldFloat := new(big.Float).SetInt(freshValue)
416+
diffFloat := new(big.Float).SetInt(diff)
417+
percentageChange := new(big.Float).Quo(diffFloat, oldFloat)
418+
percentageChange.Mul(percentageChange, big.NewFloat(100))
419+
thresholdPercent := new(big.Float).Mul(priceDevThreshold, big.NewFloat(100))
420+
absChange := new(big.Float).Abs(percentageChange)
421+
if absChange.Cmp(thresholdPercent) > 0 {
422+
logger.Infof("Monitoring triggered: price deviation threshold exceeded (incoming vs on-chain) for chain=%d contract=%s symbol=%s, "+
423+
"change=%.2f%% threshold=%.2f%% onchain_value=%s incoming_value=%s",
424+
chainID, contractAddress.Hex(), symbol, percentageChange, thresholdPercent, formatValue(freshValue), incomingPrice.String())
425+
return true
426+
}
427+
}
397428
}
398429
}
399-
}
430+
} // End of skipDeviationCheck else block
400431

401432
if timeThreshold == 0 {
402433
timeThreshold = 5 * time.Minute
403434
}
404435
totalThreshold := timeThreshold + m.timeThresholdOffset
405436

406-
timeSinceUpdate := time.Since(time.Unix(int64(lastTimestamp), 0))
437+
// Time threshold check always proceeds (not skipped by 2-minute rule)
407438
if timeSinceUpdate > totalThreshold {
408439
logger.Infof("Monitoring triggered: time threshold exceeded for chain=%d contract=%s symbol=%s, "+
409440
"time_since_update=%v threshold=%v value=%s",
@@ -414,9 +445,8 @@ func (m *OnChainMonitor) ShouldProcess(chainID int64, contractAddress common.Add
414445
return false
415446
}
416447

417-
// GetPriceDeviationThreshold returns the price deviation threshold for a destination
418448
func (m *OnChainMonitor) GetPriceDeviationThreshold(chainID int64, contractAddress common.Address, symbol string) *big.Float {
419-
key := utils.GenerateDestinationKey(chainID, contractAddress.Hex(), symbol)
449+
key := m.generateKey(chainID, contractAddress, symbol)
420450
dest := m.findDestination(key)
421451

422452
if dest == nil || dest.PriceDeviation == "" {
@@ -427,9 +457,28 @@ func (m *OnChainMonitor) GetPriceDeviationThreshold(chainID int64, contractAddre
427457
return new(big.Float).Add(priceDeviation, m.priceDeviationOffset)
428458
}
429459

430-
// GetInactivityThreshold returns the inactivity threshold for a destination
460+
func (m *OnChainMonitor) GetPriceDeviationThresholdWithOffset(chainID int64, contractAddress common.Address, symbol string) *big.Float {
461+
return m.getPriceDeviationThresholdWithOffset(chainID, contractAddress, symbol)
462+
}
463+
464+
func (m *OnChainMonitor) getPriceDeviationThresholdWithOffset(chainID int64, contractAddress common.Address, symbol string) *big.Float {
465+
key := m.generateKey(chainID, contractAddress, symbol)
466+
dest := m.findDestination(key)
467+
468+
if dest == nil || dest.PriceDeviation == "" {
469+
baseThreshold := big.NewFloat(0.10 / 100.0)
470+
offsetPercent := m.priceDeviationOffset
471+
offsetAmount := new(big.Float).Mul(baseThreshold, offsetPercent)
472+
return new(big.Float).Add(baseThreshold, offsetAmount)
473+
}
474+
475+
baseThreshold := parsePriceDeviation(dest.PriceDeviation)
476+
offsetAmount := new(big.Float).Mul(baseThreshold, m.priceDeviationOffset)
477+
return new(big.Float).Add(baseThreshold, offsetAmount)
478+
}
479+
431480
func (m *OnChainMonitor) GetInactivityThreshold(chainID int64, contractAddress common.Address, symbol string) time.Duration {
432-
key := utils.GenerateDestinationKey(chainID, contractAddress.Hex(), symbol)
481+
key := m.generateKey(chainID, contractAddress, symbol)
433482
dest := m.findDestination(key)
434483

435484
timeThreshold := 5 * time.Minute
@@ -443,6 +492,30 @@ func (m *OnChainMonitor) GetInactivityThreshold(chainID int64, contractAddress c
443492
return timeThreshold + m.timeThresholdOffset
444493
}
445494

495+
func (m *OnChainMonitor) GetTimeThresholdOffset() time.Duration {
496+
return m.timeThresholdOffset
497+
}
498+
499+
func (m *OnChainMonitor) GetMonitoringInfo(chainID int64, contractAddress common.Address, symbol string) (onChainValue *big.Int, lastTimestamp uint64, timeThreshold time.Duration) {
500+
key := m.generateKey(chainID, contractAddress, symbol)
501+
dest := m.findDestination(key)
502+
if dest == nil {
503+
return nil, 0, 0
504+
}
505+
506+
dest.mu.RLock()
507+
defer dest.mu.RUnlock()
508+
509+
onChainValue = dest.lastValue
510+
lastTimestamp = dest.lastTimestamp
511+
timeThreshold = dest.TimeThreshold.Duration()
512+
if timeThreshold == 0 {
513+
timeThreshold = 5 * time.Minute
514+
}
515+
516+
return onChainValue, lastTimestamp, timeThreshold
517+
}
518+
446519
func ParsePriceDeviation(s string) *big.Float {
447520
return parsePriceDeviation(s)
448521
}

0 commit comments

Comments
 (0)