Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: skip PublisherStalledCheck if PublisherOfflineCheck fails #71

Merged
merged 3 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"])
cctdaniel marked this conversation as resolved.
Show resolved Hide resolved

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
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
Loading