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.
Files changed (52) hide show
  1. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/CHANGELOG.md +5 -0
  2. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/PKG-INFO +1 -1
  3. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/pyproject.toml +1 -1
  4. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/data/beacon.py +60 -22
  5. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/web/app.py +1 -1
  6. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/.dockerignore +0 -0
  7. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/.env.example +0 -0
  8. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/.github/workflows/docker-publish.yaml +0 -0
  9. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/.github/workflows/release.yaml +0 -0
  10. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/.gitignore +0 -0
  11. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/Dockerfile +0 -0
  12. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/README.md +0 -0
  13. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/docker-compose.yml +0 -0
  14. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/img/csm-dash-cli.png +0 -0
  15. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/img/csm-dash-web.png +0 -0
  16. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/img/favicon.ico +0 -0
  17. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/img/logo.png +0 -0
  18. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/my-lido-csm-dashboard.xml +0 -0
  19. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/requirements.txt +0 -0
  20. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/__init__.py +0 -0
  21. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/abis/CSAccounting.json +0 -0
  22. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/abis/CSFeeDistributor.json +0 -0
  23. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/abis/CSModule.json +0 -0
  24. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/abis/WithdrawalQueueERC721.json +0 -0
  25. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/abis/__init__.py +0 -0
  26. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/abis/stETH.json +0 -0
  27. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/cli/__init__.py +0 -0
  28. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/cli/commands.py +0 -0
  29. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/core/__init__.py +0 -0
  30. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/core/config.py +0 -0
  31. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/core/contracts.py +0 -0
  32. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/core/types.py +0 -0
  33. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/data/__init__.py +0 -0
  34. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/data/cache.py +0 -0
  35. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/data/etherscan.py +0 -0
  36. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/data/ipfs_logs.py +0 -0
  37. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/data/known_cids.py +0 -0
  38. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/data/lido_api.py +0 -0
  39. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/data/onchain.py +0 -0
  40. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/data/rewards_tree.py +0 -0
  41. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/data/strikes.py +0 -0
  42. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/main.py +0 -0
  43. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/services/__init__.py +0 -0
  44. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/services/operator_service.py +0 -0
  45. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/web/__init__.py +0 -0
  46. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/src/web/routes.py +0 -0
  47. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/tests/__init__.py +0 -0
  48. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/tests/conftest.py +0 -0
  49. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/tests/unit/test_cache.py +0 -0
  50. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/tests/unit/test_config.py +0 -0
  51. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/tests/unit/test_strikes.py +0 -0
  52. {csm_dashboard-0.3.5 → csm_dashboard-0.3.6}/tests/unit/test_types.py +0 -0
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.6] - 2026-01-22
4
+
5
+ ### Added
6
+ - Retry logic and rate limiting for validator batch fetching
7
+
3
8
  ## [0.3.5] - 2026-01-05
4
9
 
5
10
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: csm-dashboard
3
- Version: 0.3.5
3
+ Version: 0.3.6
4
4
  Summary: Lido CSM Operator Dashboard for tracking validator earnings
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: fastapi>=0.104
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "csm-dashboard"
3
- version = "0.3.5"
3
+ version = "0.3.6"
4
4
  description = "Lido CSM Operator Dashboard for tracking validator earnings"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -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
- try:
188
- response = await client.get(
189
- f"{self.base_url}/validator/{pubkeys_param}",
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
- if response.status_code == 200:
194
- data = response.json().get("data", [])
195
- # API returns single object if only one validator
196
- if isinstance(data, dict):
197
- data = [data]
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
- for v in data:
200
- validators.append(self._parse_validator(v))
201
- elif response.status_code == 404:
202
- # Validators not found - create placeholder entries
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
- except Exception:
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
 
@@ -14,7 +14,7 @@ def create_app() -> FastAPI:
14
14
  app = FastAPI(
15
15
  title="CSM Operator Dashboard",
16
16
  description="Track your Lido CSM validator earnings",
17
- version="0.3.5",
17
+ version="0.3.6",
18
18
  )
19
19
 
20
20
  app.include_router(router, prefix="/api")
File without changes
File without changes
File without changes
File without changes