Skip to content

Commit

Permalink
feat: add max latency field (#59)
Browse files Browse the repository at this point in the history
  • Loading branch information
ali-bahjati authored Feb 12, 2025
1 parent e3df885 commit 65c2d92
Show file tree
Hide file tree
Showing 4 changed files with 269 additions and 87 deletions.
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

0 comments on commit 65c2d92

Please sign in to comment.