Skip to content

Commit 5cd502f

Browse files
authored
fix: skip PublisherStalledCheck if PublisherOfflineCheck fails (#71)
* ignore PublisherStalledCheck when publisher is offline * add test * update config
1 parent d8ca2b6 commit 5cd502f

File tree

5 files changed

+50
-6
lines changed

5 files changed

+50
-6
lines changed

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ ignore_missing_imports = true
44

55
[tool.poetry]
66
name = "pyth-observer"
7-
version = "0.2.7"
7+
version = "0.2.8"
88
description = "Alerts and stuff"
99
authors = []
1010
readme = "README.md"

pyth_observer/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ async def run(self):
144144
PublisherState(
145145
publisher_name=publisher_name,
146146
symbol=product.attrs["symbol"],
147+
asset_type=product.attrs["asset_type"],
147148
public_key=component.publisher_key,
148149
confidence_interval=component.latest_price_info.confidence_interval,
149150
confidence_interval_aggregate=price_account.aggregate_price_info.confidence_interval,

pyth_observer/check/publisher.py

+27
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import time
22
from dataclasses import dataclass
3+
from datetime import datetime
34
from typing import Dict, Protocol, runtime_checkable
5+
from zoneinfo import ZoneInfo
46

7+
from pythclient.calendar import is_market_open
58
from pythclient.pythaccounts import PythPriceStatus
69
from pythclient.solana import SolanaPublicKey
710

@@ -14,6 +17,7 @@
1417
class PublisherState:
1518
publisher_name: str
1619
symbol: str
20+
asset_type: str
1721
public_key: SolanaPublicKey
1822
status: PythPriceStatus
1923
aggregate_status: PythPriceStatus
@@ -139,6 +143,14 @@ def state(self) -> PublisherState:
139143
return self.__state
140144

141145
def run(self) -> bool:
146+
market_open = is_market_open(
147+
self.__state.asset_type.lower(),
148+
datetime.now(ZoneInfo("America/New_York")),
149+
)
150+
151+
if not market_open:
152+
return True
153+
142154
distance = self.__state.latest_block_slot - self.__state.slot
143155

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

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

232245
def run(self) -> bool:
246+
market_open = is_market_open(
247+
self.__state.asset_type.lower(),
248+
datetime.now(ZoneInfo("America/New_York")),
249+
)
250+
251+
if not market_open:
252+
return True
253+
254+
distance = self.__state.latest_block_slot - self.__state.slot
255+
256+
# Pass when publisher is offline because PublisherOfflineCheck will be triggered
257+
if distance >= self.__max_slot_distance:
258+
return True
259+
233260
publisher_key = (self.__state.publisher_name, self.__state.symbol)
234261
current_time = time.time()
235262
previous_price, last_change_time = PUBLISHER_CACHE.get(

sample.config.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ checks:
4747
PublisherStalledCheck:
4848
enable: true
4949
stall_time_limit: 60
50+
max_slot_distance: 25
5051
# Per-symbol config
5152
Crypto.MNGO/USD:
5253
PriceFeedOfflineCheck:

tests/test_checks_publisher.py

+20-5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ def make_state(
2323
return PublisherState(
2424
publisher_name="publisher",
2525
symbol="Crypto.BTC/USD",
26+
asset_type="Crypto",
2627
public_key=SolanaPublicKey("2hgu6Umyokvo8FfSDdMa9nDKhcdv9Q4VvGNhRCeSWeD3"),
2728
status=PythPriceStatus.TRADING,
2829
aggregate_status=PythPriceStatus.TRADING,
@@ -62,8 +63,14 @@ def simulate_time_pass(seconds):
6263
current_time += seconds
6364
return current_time
6465

65-
def setup_check(state, stall_time_limit):
66-
check = PublisherStalledCheck(state, {"stall_time_limit": stall_time_limit})
66+
def setup_check(state, stall_time_limit, max_slot_distance):
67+
check = PublisherStalledCheck(
68+
state,
69+
{
70+
"stall_time_limit": stall_time_limit,
71+
"max_slot_distance": max_slot_distance,
72+
},
73+
)
6774
PUBLISHER_CACHE[(state.publisher_name, state.symbol)] = (
6875
state.price,
6976
current_time,
@@ -76,17 +83,17 @@ def run_check(check, seconds, expected):
7683

7784
PUBLISHER_CACHE.clear()
7885
state_a = make_state(1, 100.0, 2.0, 1, 100.0, 1.0)
79-
check_a = setup_check(state_a, 5)
86+
check_a = setup_check(state_a, 5, 25)
8087
run_check(check_a, 5, True) # Should pass as it hits the limit exactly
8188

8289
PUBLISHER_CACHE.clear()
8390
state_b = make_state(1, 100.0, 2.0, 1, 100.0, 1.0)
84-
check_b = setup_check(state_b, 5)
91+
check_b = setup_check(state_b, 5, 25)
8592
run_check(check_b, 6, False) # Should fail as it exceeds the limit
8693

8794
PUBLISHER_CACHE.clear()
8895
state_c = make_state(1, 100.0, 2.0, 1, 100.0, 1.0)
89-
check_c = setup_check(state_c, 5)
96+
check_c = setup_check(state_c, 5, 25)
9097
run_check(check_c, 2, True) # Initial check should pass
9198
state_c.price = 105.0 # Change the price
9299
run_check(check_c, 3, True) # Should pass as price changes
@@ -95,3 +102,11 @@ def run_check(check, seconds, expected):
95102
run_check(
96103
check_c, 8, False
97104
) # Should fail as price stalls for too long after last change
105+
106+
# Adding a check for when the publisher is offline
107+
PUBLISHER_CACHE.clear()
108+
state_d = make_state(1, 100.0, 2.0, 1, 100.0, 1.0)
109+
state_d.latest_block_slot = 25
110+
state_d.slot = 0
111+
check_d = setup_check(state_d, 5, 25)
112+
run_check(check_d, 10, True) # Should pass as the publisher is offline

0 commit comments

Comments
 (0)