csm-dashboard 0.3.5__tar.gz → 0.3.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/CHANGELOG.md +5 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/PKG-INFO +1 -1
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/pyproject.toml +1 -1
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/data/beacon.py +60 -22
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/web/app.py +1 -1
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/.dockerignore +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/.env.example +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/.github/workflows/docker-publish.yaml +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/.github/workflows/release.yaml +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/.gitignore +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/Dockerfile +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/README.md +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/docker-compose.yml +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/img/csm-dash-cli.png +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/img/csm-dash-web.png +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/img/favicon.ico +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/img/logo.png +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/my-lido-csm-dashboard.xml +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/requirements.txt +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/__init__.py +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/abis/CSAccounting.json +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/abis/CSFeeDistributor.json +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/abis/CSModule.json +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/abis/WithdrawalQueueERC721.json +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/abis/__init__.py +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/abis/stETH.json +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/cli/__init__.py +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/cli/commands.py +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/core/__init__.py +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/core/config.py +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/core/contracts.py +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/core/types.py +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/data/__init__.py +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/data/cache.py +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/data/etherscan.py +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/data/ipfs_logs.py +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/data/known_cids.py +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/data/lido_api.py +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/data/onchain.py +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/data/rewards_tree.py +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/data/strikes.py +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/main.py +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/services/__init__.py +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/services/operator_service.py +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/web/__init__.py +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/web/routes.py +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/tests/__init__.py +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/tests/conftest.py +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/tests/unit/test_cache.py +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/tests/unit/test_config.py +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/tests/unit/test_strikes.py +0 -0
- {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/tests/unit/test_types.py +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Beacon chain data fetching via beaconcha.in API."""
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
3
4
|
from datetime import datetime, timedelta, timezone
|
|
4
5
|
from decimal import Decimal
|
|
5
6
|
from enum import Enum
|
|
@@ -172,47 +173,84 @@ class BeaconDataProvider:
|
|
|
172
173
|
Fetch validator info for multiple pubkeys.
|
|
173
174
|
|
|
174
175
|
beaconcha.in supports comma-separated pubkeys (up to 100).
|
|
176
|
+
Includes retry logic for rate limiting and proper error handling.
|
|
175
177
|
"""
|
|
176
178
|
if not pubkeys:
|
|
177
179
|
return []
|
|
178
180
|
|
|
179
181
|
validators = []
|
|
180
182
|
batch_size = 100 # beaconcha.in limit
|
|
183
|
+
max_retries = 3
|
|
181
184
|
|
|
182
185
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
183
186
|
for i in range(0, len(pubkeys), batch_size):
|
|
184
187
|
batch = pubkeys[i : i + batch_size]
|
|
185
188
|
pubkeys_param = ",".join(batch)
|
|
186
189
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
headers=self._get_headers(),
|
|
191
|
-
)
|
|
190
|
+
# Add delay between batches to avoid rate limiting
|
|
191
|
+
if i > 0:
|
|
192
|
+
await asyncio.sleep(0.5)
|
|
192
193
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
194
|
+
for attempt in range(max_retries):
|
|
195
|
+
try:
|
|
196
|
+
response = await client.get(
|
|
197
|
+
f"{self.base_url}/validator/{pubkeys_param}",
|
|
198
|
+
headers=self._get_headers(),
|
|
199
|
+
)
|
|
198
200
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
201
|
+
if response.status_code == 200:
|
|
202
|
+
data = response.json().get("data", [])
|
|
203
|
+
# API returns single object if only one validator
|
|
204
|
+
if isinstance(data, dict):
|
|
205
|
+
data = [data]
|
|
206
|
+
|
|
207
|
+
for v in data:
|
|
208
|
+
validators.append(self._parse_validator(v))
|
|
209
|
+
break # Success, exit retry loop
|
|
210
|
+
elif response.status_code == 404:
|
|
211
|
+
# Validators not found - create placeholder entries
|
|
212
|
+
for pubkey in batch:
|
|
213
|
+
validators.append(
|
|
214
|
+
ValidatorInfo(
|
|
215
|
+
pubkey=pubkey,
|
|
216
|
+
status=ValidatorStatus.PENDING_INITIALIZED,
|
|
217
|
+
)
|
|
218
|
+
)
|
|
219
|
+
break # Success, exit retry loop
|
|
220
|
+
elif response.status_code == 429:
|
|
221
|
+
# Rate limited - wait and retry
|
|
222
|
+
if attempt < max_retries - 1:
|
|
223
|
+
await asyncio.sleep(2**attempt) # 1s, 2s, 4s
|
|
224
|
+
continue
|
|
225
|
+
# Max retries reached, add as unknown
|
|
226
|
+
for pubkey in batch:
|
|
227
|
+
validators.append(
|
|
228
|
+
ValidatorInfo(
|
|
229
|
+
pubkey=pubkey, status=ValidatorStatus.UNKNOWN
|
|
230
|
+
)
|
|
231
|
+
)
|
|
232
|
+
break
|
|
233
|
+
else:
|
|
234
|
+
# Other error status - add as unknown
|
|
235
|
+
for pubkey in batch:
|
|
236
|
+
validators.append(
|
|
237
|
+
ValidatorInfo(
|
|
238
|
+
pubkey=pubkey, status=ValidatorStatus.UNKNOWN
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
break
|
|
242
|
+
except Exception:
|
|
243
|
+
if attempt < max_retries - 1:
|
|
244
|
+
await asyncio.sleep(1)
|
|
245
|
+
continue
|
|
246
|
+
# On final failure, add unknown status for this batch
|
|
203
247
|
for pubkey in batch:
|
|
204
248
|
validators.append(
|
|
205
249
|
ValidatorInfo(
|
|
206
|
-
pubkey=pubkey,
|
|
207
|
-
status=ValidatorStatus.PENDING_INITIALIZED,
|
|
250
|
+
pubkey=pubkey, status=ValidatorStatus.UNKNOWN
|
|
208
251
|
)
|
|
209
252
|
)
|
|
210
|
-
|
|
211
|
-
# On error, add unknown status for this batch
|
|
212
|
-
for pubkey in batch:
|
|
213
|
-
validators.append(
|
|
214
|
-
ValidatorInfo(pubkey=pubkey, status=ValidatorStatus.UNKNOWN)
|
|
215
|
-
)
|
|
253
|
+
break
|
|
216
254
|
|
|
217
255
|
return validators
|
|
218
256
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|