fix: harden wallet balances and service startup
This commit is contained in:
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import hashlib
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from decimal import Decimal, InvalidOperation
|
||||
from typing import Any
|
||||
@@ -19,6 +20,8 @@ from src.infrastructure.wallet_balances.token_registry import (
|
||||
)
|
||||
|
||||
TIMEOUT_SECONDS = 15
|
||||
RETRY_ATTEMPTS = 2
|
||||
PRICE_CACHE_SECONDS = 30
|
||||
BLOCKSTREAM_URL = 'https://blockstream.info/api'
|
||||
TRONGRID_URL = 'https://api.trongrid.io'
|
||||
COINGECKO_URL = 'https://api.coingecko.com/api/v3/simple/price'
|
||||
@@ -49,6 +52,10 @@ class WalletBalance:
|
||||
|
||||
|
||||
class WalletBalanceService:
|
||||
def __init__(self) -> None:
|
||||
self._prices_cache: dict[tuple[tuple[str,str],...],tuple[float,dict[str,Decimal | None]]] = {}
|
||||
|
||||
|
||||
async def get_wallet_balances(self, wallets) -> list[WalletBalance]:
|
||||
prices = await self._get_prices(wallets)
|
||||
results = await asyncio.gather(
|
||||
@@ -193,10 +200,17 @@ class WalletBalanceService:
|
||||
for token in self._tokens_for_chain(chain):
|
||||
coin_id = get_coingecko_id(chain, token['symbol'])
|
||||
if coin_id:
|
||||
pairs[f'{chain}:{token['symbol']}'] = coin_id
|
||||
symbol = token['symbol']
|
||||
pairs[f'{chain}:{symbol}'] = coin_id
|
||||
if not pairs:
|
||||
return {}
|
||||
|
||||
cache_key = tuple(sorted(pairs.items()))
|
||||
cached = self._prices_cache.get(cache_key)
|
||||
now = time.monotonic()
|
||||
if cached is not None and now - cached[0] < PRICE_CACHE_SECONDS:
|
||||
return dict(cached[1])
|
||||
|
||||
try:
|
||||
response = await self._get_json(
|
||||
COINGECKO_URL,
|
||||
@@ -210,10 +224,12 @@ class WalletBalanceService:
|
||||
except Exception:
|
||||
return {key: None for key in pairs}
|
||||
|
||||
return {
|
||||
prices = {
|
||||
key: self._decimal_or_none((response.get(coin_id) or {}).get('usd'))
|
||||
for key, coin_id in pairs.items()
|
||||
}
|
||||
self._prices_cache[cache_key] = (now,prices)
|
||||
return prices
|
||||
|
||||
def _token_amounts(
|
||||
self,
|
||||
@@ -287,9 +303,16 @@ class WalletBalanceService:
|
||||
timeout: int = TIMEOUT_SECONDS,
|
||||
) -> Any:
|
||||
async with httpx.AsyncClient(timeout=timeout) as client:
|
||||
response = await client.get(url, params=params, headers=headers)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
for attempt in range(RETRY_ATTEMPTS + 1):
|
||||
try:
|
||||
response = await client.get(url, params=params, headers=headers)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except (httpx.RequestError,httpx.HTTPStatusError,ValueError):
|
||||
if attempt >= RETRY_ATTEMPTS:
|
||||
raise
|
||||
await asyncio.sleep(0.2 * (attempt + 1))
|
||||
raise RuntimeError('HTTP GET failed')
|
||||
|
||||
async def _post_json(
|
||||
self,
|
||||
@@ -300,9 +323,16 @@ class WalletBalanceService:
|
||||
timeout: int = TIMEOUT_SECONDS,
|
||||
) -> Any:
|
||||
async with httpx.AsyncClient(timeout=timeout) as client:
|
||||
response = await client.post(url, json=payload, headers=headers)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
for attempt in range(RETRY_ATTEMPTS + 1):
|
||||
try:
|
||||
response = await client.post(url, json=payload, headers=headers)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except (httpx.RequestError,httpx.HTTPStatusError,ValueError):
|
||||
if attempt >= RETRY_ATTEMPTS:
|
||||
raise
|
||||
await asyncio.sleep(0.2 * (attempt + 1))
|
||||
raise RuntimeError('HTTP POST failed')
|
||||
|
||||
def _tokens_for_chain(self, chain: str) -> list[dict]:
|
||||
if chain in {'ETH', 'BSC'}:
|
||||
|
||||
Reference in New Issue
Block a user