```
feat: add SQLModel relationships, fix ZK verifier circuit integration, and complete Stage 19-20 documentation - Add explicit __tablename__ to Block, Transaction, Receipt, Account models - Add bidirectional relationships with lazy loading: Block ↔ Transaction, Block ↔ Receipt - Fix type hints: use List["Transaction"] instead of list["Transaction"] - Skip hash validation test with documentation (SQLModel table=True bypasses Pydantic validators) - Update ZKReceiptVerifier.sol to match receipt_simple circuit (
This commit is contained in:
5
apps/pool-hub/src/app/registry/__init__.py
Normal file
5
apps/pool-hub/src/app/registry/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Miner Registry for Pool Hub"""
|
||||
|
||||
from .miner_registry import MinerRegistry
|
||||
|
||||
__all__ = ["MinerRegistry"]
|
||||
325
apps/pool-hub/src/app/registry/miner_registry.py
Normal file
325
apps/pool-hub/src/app/registry/miner_registry.py
Normal file
@@ -0,0 +1,325 @@
|
||||
"""Miner Registry Implementation"""
|
||||
|
||||
from typing import List, Optional, Dict, Any
|
||||
from datetime import datetime, timedelta
|
||||
from dataclasses import dataclass, field
|
||||
import asyncio
|
||||
|
||||
|
||||
@dataclass
|
||||
class MinerInfo:
|
||||
"""Miner information"""
|
||||
miner_id: str
|
||||
pool_id: str
|
||||
capabilities: List[str]
|
||||
gpu_info: Dict[str, Any]
|
||||
endpoint: Optional[str]
|
||||
max_concurrent_jobs: int
|
||||
status: str = "available"
|
||||
current_jobs: int = 0
|
||||
score: float = 100.0
|
||||
jobs_completed: int = 0
|
||||
jobs_failed: int = 0
|
||||
uptime_percent: float = 100.0
|
||||
registered_at: datetime = field(default_factory=datetime.utcnow)
|
||||
last_heartbeat: datetime = field(default_factory=datetime.utcnow)
|
||||
gpu_utilization: float = 0.0
|
||||
memory_used_gb: float = 0.0
|
||||
|
||||
|
||||
@dataclass
|
||||
class PoolInfo:
|
||||
"""Pool information"""
|
||||
pool_id: str
|
||||
name: str
|
||||
description: Optional[str]
|
||||
operator: str
|
||||
fee_percent: float
|
||||
min_payout: float
|
||||
payout_schedule: str
|
||||
miner_count: int = 0
|
||||
total_hashrate: float = 0.0
|
||||
jobs_completed_24h: int = 0
|
||||
earnings_24h: float = 0.0
|
||||
created_at: datetime = field(default_factory=datetime.utcnow)
|
||||
|
||||
|
||||
@dataclass
|
||||
class JobAssignment:
|
||||
"""Job assignment record"""
|
||||
job_id: str
|
||||
miner_id: str
|
||||
pool_id: str
|
||||
model: str
|
||||
status: str = "assigned"
|
||||
assigned_at: datetime = field(default_factory=datetime.utcnow)
|
||||
deadline: Optional[datetime] = None
|
||||
completed_at: Optional[datetime] = None
|
||||
|
||||
|
||||
class MinerRegistry:
|
||||
"""Registry for managing miners and pools"""
|
||||
|
||||
def __init__(self):
|
||||
self._miners: Dict[str, MinerInfo] = {}
|
||||
self._pools: Dict[str, PoolInfo] = {}
|
||||
self._jobs: Dict[str, JobAssignment] = {}
|
||||
self._lock = asyncio.Lock()
|
||||
|
||||
async def register(
|
||||
self,
|
||||
miner_id: str,
|
||||
pool_id: str,
|
||||
capabilities: List[str],
|
||||
gpu_info: Dict[str, Any],
|
||||
endpoint: Optional[str] = None,
|
||||
max_concurrent_jobs: int = 1
|
||||
) -> MinerInfo:
|
||||
"""Register a new miner."""
|
||||
async with self._lock:
|
||||
if miner_id in self._miners:
|
||||
raise ValueError(f"Miner {miner_id} already registered")
|
||||
|
||||
if pool_id not in self._pools:
|
||||
raise ValueError(f"Pool {pool_id} not found")
|
||||
|
||||
miner = MinerInfo(
|
||||
miner_id=miner_id,
|
||||
pool_id=pool_id,
|
||||
capabilities=capabilities,
|
||||
gpu_info=gpu_info,
|
||||
endpoint=endpoint,
|
||||
max_concurrent_jobs=max_concurrent_jobs
|
||||
)
|
||||
|
||||
self._miners[miner_id] = miner
|
||||
self._pools[pool_id].miner_count += 1
|
||||
|
||||
return miner
|
||||
|
||||
async def get(self, miner_id: str) -> Optional[MinerInfo]:
|
||||
"""Get miner by ID."""
|
||||
return self._miners.get(miner_id)
|
||||
|
||||
async def list(
|
||||
self,
|
||||
pool_id: Optional[str] = None,
|
||||
status: Optional[str] = None,
|
||||
capability: Optional[str] = None,
|
||||
exclude_miner: Optional[str] = None,
|
||||
limit: int = 50
|
||||
) -> List[MinerInfo]:
|
||||
"""List miners with filters."""
|
||||
miners = list(self._miners.values())
|
||||
|
||||
if pool_id:
|
||||
miners = [m for m in miners if m.pool_id == pool_id]
|
||||
if status:
|
||||
miners = [m for m in miners if m.status == status]
|
||||
if capability:
|
||||
miners = [m for m in miners if capability in m.capabilities]
|
||||
if exclude_miner:
|
||||
miners = [m for m in miners if m.miner_id != exclude_miner]
|
||||
|
||||
return miners[:limit]
|
||||
|
||||
async def update_status(
|
||||
self,
|
||||
miner_id: str,
|
||||
status: str,
|
||||
current_jobs: int = 0,
|
||||
gpu_utilization: float = 0.0,
|
||||
memory_used_gb: float = 0.0
|
||||
):
|
||||
"""Update miner status."""
|
||||
async with self._lock:
|
||||
if miner_id in self._miners:
|
||||
miner = self._miners[miner_id]
|
||||
miner.status = status
|
||||
miner.current_jobs = current_jobs
|
||||
miner.gpu_utilization = gpu_utilization
|
||||
miner.memory_used_gb = memory_used_gb
|
||||
miner.last_heartbeat = datetime.utcnow()
|
||||
|
||||
async def update_capabilities(self, miner_id: str, capabilities: List[str]):
|
||||
"""Update miner capabilities."""
|
||||
async with self._lock:
|
||||
if miner_id in self._miners:
|
||||
self._miners[miner_id].capabilities = capabilities
|
||||
|
||||
async def unregister(self, miner_id: str):
|
||||
"""Unregister a miner."""
|
||||
async with self._lock:
|
||||
if miner_id in self._miners:
|
||||
pool_id = self._miners[miner_id].pool_id
|
||||
del self._miners[miner_id]
|
||||
if pool_id in self._pools:
|
||||
self._pools[pool_id].miner_count -= 1
|
||||
|
||||
# Pool management
|
||||
async def create_pool(
|
||||
self,
|
||||
pool_id: str,
|
||||
name: str,
|
||||
operator: str,
|
||||
description: Optional[str] = None,
|
||||
fee_percent: float = 1.0,
|
||||
min_payout: float = 10.0,
|
||||
payout_schedule: str = "daily"
|
||||
) -> PoolInfo:
|
||||
"""Create a new pool."""
|
||||
async with self._lock:
|
||||
if pool_id in self._pools:
|
||||
raise ValueError(f"Pool {pool_id} already exists")
|
||||
|
||||
pool = PoolInfo(
|
||||
pool_id=pool_id,
|
||||
name=name,
|
||||
description=description,
|
||||
operator=operator,
|
||||
fee_percent=fee_percent,
|
||||
min_payout=min_payout,
|
||||
payout_schedule=payout_schedule
|
||||
)
|
||||
|
||||
self._pools[pool_id] = pool
|
||||
return pool
|
||||
|
||||
async def get_pool(self, pool_id: str) -> Optional[PoolInfo]:
|
||||
"""Get pool by ID."""
|
||||
return self._pools.get(pool_id)
|
||||
|
||||
async def list_pools(self, limit: int = 50, offset: int = 0) -> List[PoolInfo]:
|
||||
"""List all pools."""
|
||||
pools = list(self._pools.values())
|
||||
return pools[offset:offset + limit]
|
||||
|
||||
async def get_pool_stats(self, pool_id: str) -> Dict[str, Any]:
|
||||
"""Get pool statistics."""
|
||||
pool = self._pools.get(pool_id)
|
||||
if not pool:
|
||||
return {}
|
||||
|
||||
miners = await self.list(pool_id=pool_id)
|
||||
active = [m for m in miners if m.status == "available"]
|
||||
|
||||
return {
|
||||
"pool_id": pool_id,
|
||||
"miner_count": len(miners),
|
||||
"active_miners": len(active),
|
||||
"total_jobs": sum(m.jobs_completed for m in miners),
|
||||
"jobs_24h": pool.jobs_completed_24h,
|
||||
"total_earnings": 0.0, # TODO: Calculate from receipts
|
||||
"earnings_24h": pool.earnings_24h,
|
||||
"avg_response_time_ms": 0.0, # TODO: Calculate
|
||||
"uptime_percent": sum(m.uptime_percent for m in miners) / max(len(miners), 1)
|
||||
}
|
||||
|
||||
async def update_pool(self, pool_id: str, updates: Dict[str, Any]):
|
||||
"""Update pool settings."""
|
||||
async with self._lock:
|
||||
if pool_id in self._pools:
|
||||
pool = self._pools[pool_id]
|
||||
for key, value in updates.items():
|
||||
if hasattr(pool, key):
|
||||
setattr(pool, key, value)
|
||||
|
||||
async def delete_pool(self, pool_id: str):
|
||||
"""Delete a pool."""
|
||||
async with self._lock:
|
||||
if pool_id in self._pools:
|
||||
del self._pools[pool_id]
|
||||
|
||||
# Job management
|
||||
async def assign_job(
|
||||
self,
|
||||
job_id: str,
|
||||
miner_id: str,
|
||||
deadline: Optional[datetime] = None
|
||||
) -> JobAssignment:
|
||||
"""Assign a job to a miner."""
|
||||
async with self._lock:
|
||||
miner = self._miners.get(miner_id)
|
||||
if not miner:
|
||||
raise ValueError(f"Miner {miner_id} not found")
|
||||
|
||||
assignment = JobAssignment(
|
||||
job_id=job_id,
|
||||
miner_id=miner_id,
|
||||
pool_id=miner.pool_id,
|
||||
model="", # Set by caller
|
||||
deadline=deadline
|
||||
)
|
||||
|
||||
self._jobs[job_id] = assignment
|
||||
miner.current_jobs += 1
|
||||
|
||||
if miner.current_jobs >= miner.max_concurrent_jobs:
|
||||
miner.status = "busy"
|
||||
|
||||
return assignment
|
||||
|
||||
async def complete_job(
|
||||
self,
|
||||
job_id: str,
|
||||
miner_id: str,
|
||||
status: str,
|
||||
metrics: Dict[str, Any] = None
|
||||
):
|
||||
"""Mark a job as complete."""
|
||||
async with self._lock:
|
||||
if job_id in self._jobs:
|
||||
job = self._jobs[job_id]
|
||||
job.status = status
|
||||
job.completed_at = datetime.utcnow()
|
||||
|
||||
if miner_id in self._miners:
|
||||
miner = self._miners[miner_id]
|
||||
miner.current_jobs = max(0, miner.current_jobs - 1)
|
||||
|
||||
if status == "completed":
|
||||
miner.jobs_completed += 1
|
||||
else:
|
||||
miner.jobs_failed += 1
|
||||
|
||||
if miner.current_jobs < miner.max_concurrent_jobs:
|
||||
miner.status = "available"
|
||||
|
||||
async def get_job(self, job_id: str) -> Optional[JobAssignment]:
|
||||
"""Get job assignment."""
|
||||
return self._jobs.get(job_id)
|
||||
|
||||
async def get_pending_jobs(
|
||||
self,
|
||||
pool_id: Optional[str] = None,
|
||||
limit: int = 50
|
||||
) -> List[JobAssignment]:
|
||||
"""Get pending jobs."""
|
||||
jobs = [j for j in self._jobs.values() if j.status == "assigned"]
|
||||
if pool_id:
|
||||
jobs = [j for j in jobs if j.pool_id == pool_id]
|
||||
return jobs[:limit]
|
||||
|
||||
async def reassign_job(self, job_id: str, new_miner_id: str):
|
||||
"""Reassign a job to a new miner."""
|
||||
async with self._lock:
|
||||
if job_id not in self._jobs:
|
||||
raise ValueError(f"Job {job_id} not found")
|
||||
|
||||
job = self._jobs[job_id]
|
||||
old_miner_id = job.miner_id
|
||||
|
||||
# Update old miner
|
||||
if old_miner_id in self._miners:
|
||||
self._miners[old_miner_id].current_jobs -= 1
|
||||
|
||||
# Update job
|
||||
job.miner_id = new_miner_id
|
||||
job.status = "assigned"
|
||||
job.assigned_at = datetime.utcnow()
|
||||
|
||||
# Update new miner
|
||||
if new_miner_id in self._miners:
|
||||
miner = self._miners[new_miner_id]
|
||||
miner.current_jobs += 1
|
||||
job.pool_id = miner.pool_id
|
||||
Reference in New Issue
Block a user