Skip to content

Commit

Permalink
fix: skip PublisherStalledCheck if PublisherOfflineCheck fails (#71)
Browse files Browse the repository at this point in the history
* ignore PublisherStalledCheck when publisher is offline

* add test

* update config
  • Loading branch information
cctdaniel authored May 20, 2024
1 parent d8ca2b6 commit 5cd502f
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 6 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ ignore_missing_imports = true

[tool.poetry]
name = "pyth-observer"
version = "0.2.7"
version = "0.2.8"
description = "Alerts and stuff"
authors = []
readme = "README.md"
Expand Down
1 change: 1 addition & 0 deletions pyth_observer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ async def run(self):
PublisherState(
publisher_name=publisher_name,
symbol=product.attrs["symbol"],
asset_type=product.attrs["asset_type"],
public_key=component.publisher_key,
confidence_interval=component.latest_price_info.confidence_interval,
confidence_interval_aggregate=price_account.aggregate_price_info.confidence_interval,
Expand Down
27 changes: 27 additions & 0 deletions pyth_observer/check/publisher.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import time
from dataclasses import dataclass
from datetime import datetime
from typing import Dict, Protocol, runtime_checkable
from zoneinfo import ZoneInfo

from pythclient.calendar import is_market_open
from pythclient.pythaccounts import PythPriceStatus
from pythclient.solana import SolanaPublicKey

Expand All @@ -14,6 +17,7 @@
class PublisherState:
publisher_name: str
symbol: str
asset_type: str
public_key: SolanaPublicKey
status: PythPriceStatus
aggregate_status: PythPriceStatus
Expand Down Expand Up @@ -139,6 +143,14 @@ def state(self) -> PublisherState:
return self.__state

def run(self) -> bool:
market_open = is_market_open(
self.__state.asset_type.lower(),
datetime.now(ZoneInfo("America/New_York")),
)

if not market_open:
return True

distance = self.__state.latest_block_slot - self.__state.slot

# Pass if publisher slot is not too far from aggregate slot
Expand Down Expand Up @@ -225,11 +237,26 @@ def __init__(self, state: PublisherState, config: PublisherCheckConfig):
self.__stall_time_limit: int = int(
config["stall_time_limit"]
) # Time in seconds
self.__max_slot_distance: int = int(config["max_slot_distance"])

def state(self) -> PublisherState:
return self.__state

def run(self) -> bool:
market_open = is_market_open(
self.__state.asset_type.lower(),
datetime.now(ZoneInfo("America/New_York")),
)

if not market_open:
return True

distance = self.__state.latest_block_slot - self.__state.slot

# Pass when publisher is offline because PublisherOfflineCheck will be triggered
if distance >= self.__max_slot_distance:
return True

publisher_key = (self.__state.publisher_name, self.__state.symbol)
current_time = time.time()
previous_price, last_change_time = PUBLISHER_CACHE.get(
Expand Down
1 change: 1 addition & 0 deletions sample.config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ checks:
PublisherStalledCheck:
enable: true
stall_time_limit: 60
max_slot_distance: 25
# Per-symbol config
Crypto.MNGO/USD:
PriceFeedOfflineCheck:
Expand Down
25 changes: 20 additions & 5 deletions tests/test_checks_publisher.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def make_state(
return PublisherState(
publisher_name="publisher",
symbol="Crypto.BTC/USD",
asset_type="Crypto",
public_key=SolanaPublicKey("2hgu6Umyokvo8FfSDdMa9nDKhcdv9Q4VvGNhRCeSWeD3"),
status=PythPriceStatus.TRADING,
aggregate_status=PythPriceStatus.TRADING,
Expand Down Expand Up @@ -62,8 +63,14 @@ def simulate_time_pass(seconds):
current_time += seconds
return current_time

def setup_check(state, stall_time_limit):
check = PublisherStalledCheck(state, {"stall_time_limit": stall_time_limit})
def setup_check(state, stall_time_limit, max_slot_distance):
check = PublisherStalledCheck(
state,
{
"stall_time_limit": stall_time_limit,
"max_slot_distance": max_slot_distance,
},
)
PUBLISHER_CACHE[(state.publisher_name, state.symbol)] = (
state.price,
current_time,
Expand All @@ -76,17 +83,17 @@ def run_check(check, seconds, expected):

PUBLISHER_CACHE.clear()
state_a = make_state(1, 100.0, 2.0, 1, 100.0, 1.0)
check_a = setup_check(state_a, 5)
check_a = setup_check(state_a, 5, 25)
run_check(check_a, 5, True) # Should pass as it hits the limit exactly

PUBLISHER_CACHE.clear()
state_b = make_state(1, 100.0, 2.0, 1, 100.0, 1.0)
check_b = setup_check(state_b, 5)
check_b = setup_check(state_b, 5, 25)
run_check(check_b, 6, False) # Should fail as it exceeds the limit

PUBLISHER_CACHE.clear()
state_c = make_state(1, 100.0, 2.0, 1, 100.0, 1.0)
check_c = setup_check(state_c, 5)
check_c = setup_check(state_c, 5, 25)
run_check(check_c, 2, True) # Initial check should pass
state_c.price = 105.0 # Change the price
run_check(check_c, 3, True) # Should pass as price changes
Expand All @@ -95,3 +102,11 @@ def run_check(check, seconds, expected):
run_check(
check_c, 8, False
) # Should fail as price stalls for too long after last change

# Adding a check for when the publisher is offline
PUBLISHER_CACHE.clear()
state_d = make_state(1, 100.0, 2.0, 1, 100.0, 1.0)
state_d.latest_block_slot = 25
state_d.slot = 0
check_d = setup_check(state_d, 5, 25)
run_check(check_d, 10, True) # Should pass as the publisher is offline

0 comments on commit 5cd502f

Please sign in to comment.