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

feat: add max latency field #59

Merged
merged 1 commit into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
21 changes: 14 additions & 7 deletions pythclient/pythaccounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
_VERSION_1 = 1
_VERSION_2 = 2
_SUPPORTED_VERSIONS = set((_VERSION_1, _VERSION_2))
_ACCOUNT_HEADER_BYTES = 16 # magic + version + type + size, u32 * 4
ACCOUNT_HEADER_BYTES = 16 # magic + version + type + size, u32 * 4
_NULL_KEY_BYTES = b'\x00' * SolanaPublicKey.LENGTH
MAX_SLOT_DIFFERENCE = 25
DEFAULT_MAX_LATENCY = 25


class PythAccountType(Enum):
Expand Down Expand Up @@ -81,7 +81,7 @@ def _read_attribute_string(buffer: bytes, offset: int) -> Tuple[Optional[str], i


def _parse_header(buffer: bytes, offset: int = 0, *, key: SolanaPublicKeyOrStr) -> Tuple[PythAccountType, int, int]:
if len(buffer) - offset < _ACCOUNT_HEADER_BYTES:
if len(buffer) - offset < ACCOUNT_HEADER_BYTES:
raise ValueError("Pyth account data too short")

# Pyth magic (u32) == MAGIC
Expand Down Expand Up @@ -141,7 +141,7 @@ def update_with_rpc_response(self, slot: int, value: Dict[str, Any]) -> None:
f"wrong Pyth account type {type_} for {type(self)}")

try:
self.update_from(data[:size], version=version, offset=_ACCOUNT_HEADER_BYTES)
self.update_from(data[:size], version=version, offset=ACCOUNT_HEADER_BYTES)
except Exception as e:
logger.exception("error while parsing account", exception=e)

Expand Down Expand Up @@ -482,6 +482,7 @@ class PythPriceAccount(PythAccount):
aggregate price is composed of
slot (int): the slot time when this account was last fetched
product (Optional[PythProductAccount]): the product this price is for, if loaded
max_latency (int): the maximum allowed slot difference for this feed
"""

def __init__(self, key: SolanaPublicKey, solana: SolanaClient, *, product: Optional[PythProductAccount] = None) -> None:
Expand All @@ -503,6 +504,7 @@ def __init__(self, key: SolanaPublicKey, solana: SolanaClient, *, product: Optio
self.prev_price: float = field(init=False)
self.prev_conf: float = field(init=False)
self.prev_timestamp: int = 0 # unix timestamp in seconds
self.max_latency: int = 0 # maximum allowed slot difference for this feed

@property
def aggregate_price(self) -> Optional[float]:
Expand Down Expand Up @@ -537,7 +539,7 @@ def get_aggregate_price_status_with_slot(self, slot: int) -> Optional[PythPriceS
You might consider using this function with the latest solana slot to make sure the price has not gone stale.
"""
if self.aggregate_price_info.price_status == PythPriceStatus.TRADING and \
slot - self.aggregate_price_info.pub_slot > MAX_SLOT_DIFFERENCE:
slot - self.aggregate_price_info.pub_slot > self.max_latency:
return PythPriceStatus.UNKNOWN

return self.aggregate_price_info.price_status
Expand Down Expand Up @@ -571,9 +573,12 @@ def update_from(self, buffer: bytes, *, version: int, offset: int = 0) -> None:
derivations = list(struct.unpack_from("<6q", buffer, offset))
self.derivations = dict((type_, derivations[type_.value - 1]) for type_ in [EmaType.EMA_CONFIDENCE_VALUE, EmaType.EMA_PRICE_VALUE])
offset += 48 # struct.calcsize("6q")
# drv[2-4]_ fields are currently unused
timestamp, min_publishers = struct.unpack_from("<qB", buffer, offset)
offset += 16 # struct.calcsize("qBbhi") ("bhi" is drv_2, drv_3, drv_4)
offset += 9 # struct.calcsize("qB")
_message_sent, max_latency = struct.unpack_from("<bB", buffer, offset)
offset += 2 # struct.calcsize("bB")
_drv_3, _drv_4 = struct.unpack_from("<bi", buffer, offset)
offset += 5 # struct.calcsize("bi")
product_account_key_bytes, next_price_account_key_bytes = struct.unpack_from("32s32s", buffer, offset)
offset += 64 # struct.calcsize("32s32s")
prev_slot, prev_price, prev_conf, prev_timestamp = struct.unpack_from("<QqQq", buffer, offset)
Expand Down Expand Up @@ -620,6 +625,8 @@ def update_from(self, buffer: bytes, *, version: int, offset: int = 0) -> None:
self.prev_price = prev_price
self.prev_conf = prev_conf
self.prev_timestamp = prev_timestamp
# a max latency of 0 is the default max latency
self.max_latency = max_latency if max_latency != 0 else DEFAULT_MAX_LATENCY

def __str__(self) -> str:
if self.product:
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

setup(
name='pythclient',
version='0.2.0',
version='0.2.1',
packages=['pythclient'],
author='Pyth Developers',
author_email='contact@pyth.network',
Expand Down
Loading
Loading