Files
blackroad-operating-system/backend/app/routers/miner.py
2025-11-16 06:41:33 -06:00

320 lines
9.5 KiB
Python

"""Mining statistics and control router - RoadCoin Miner integration."""
import asyncio
import hashlib
import random
from datetime import datetime, timedelta
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, desc
from pydantic import BaseModel
from app.database import get_db
from app.models.blockchain import Block, Wallet
from app.models.user import User
from app.routers.auth import get_current_user
from app.utils import utc_now
router = APIRouter(prefix="/api/miner", tags=["miner"])
# In-memory miner state (for simulation)
class MinerState:
"""Global miner state."""
def __init__(self):
self.is_mining = False
self.hashrate_mhs = 0.0
self.shares_submitted = 0
self.shares_accepted = 0
self.pool_url = "pool.roadcoin.network:3333"
self.worker_id = "RoadMiner-1"
self.started_at: Optional[datetime] = None
self.temperature_celsius = 65.0
self.power_watts = 120.0
miner_state = MinerState()
# Schemas
class MinerStatus(BaseModel):
"""Current miner status."""
is_mining: bool
hashrate_mhs: float
shares_submitted: int
shares_accepted: int
shares_rejected: int
pool_url: str
worker_id: str
uptime_seconds: int
temperature_celsius: float
power_watts: float
efficiency_mhs_per_watt: float
class MinerStats(BaseModel):
"""Miner statistics."""
blocks_mined: int
roadcoins_earned: float
current_hashrate_mhs: float
average_hashrate_mhs: float
total_shares: int
accepted_shares: int
rejected_shares: int
last_block_time: Optional[datetime]
mining_since: Optional[datetime]
class MinerControl(BaseModel):
"""Miner control commands."""
action: str # start, stop, restart
pool_url: Optional[str] = None
worker_id: Optional[str] = None
class RecentBlock(BaseModel):
"""Recent mined block info."""
block_index: int
block_hash: str
reward: float
timestamp: datetime
difficulty: int
class Config:
from_attributes = True
# Routes
@router.get("/status", response_model=MinerStatus)
async def get_miner_status(
current_user: User = Depends(get_current_user),
):
"""Get current miner status and performance metrics."""
uptime_seconds = 0
if miner_state.started_at:
uptime_seconds = int((utc_now() - miner_state.started_at).total_seconds())
# Simulate some variance in hashrate
current_hashrate = miner_state.hashrate_mhs
if miner_state.is_mining:
current_hashrate = miner_state.hashrate_mhs + random.uniform(-2.0, 2.0)
# Calculate efficiency
efficiency = 0.0
if miner_state.power_watts > 0:
efficiency = current_hashrate / miner_state.power_watts
rejected_shares = miner_state.shares_submitted - miner_state.shares_accepted
return MinerStatus(
is_mining=miner_state.is_mining,
hashrate_mhs=round(current_hashrate, 2),
shares_submitted=miner_state.shares_submitted,
shares_accepted=miner_state.shares_accepted,
shares_rejected=rejected_shares,
pool_url=miner_state.pool_url,
worker_id=miner_state.worker_id,
uptime_seconds=uptime_seconds,
temperature_celsius=miner_state.temperature_celsius,
power_watts=miner_state.power_watts,
efficiency_mhs_per_watt=round(efficiency, 4),
)
@router.get("/stats", response_model=MinerStats)
async def get_miner_stats(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Get overall mining statistics."""
# Get user's wallet
wallet_result = await db.execute(
select(Wallet).filter(Wallet.user_id == current_user.id)
)
wallet = wallet_result.scalar_one_or_none()
if not wallet:
return MinerStats(
blocks_mined=0,
roadcoins_earned=0.0,
current_hashrate_mhs=miner_state.hashrate_mhs if miner_state.is_mining else 0.0,
average_hashrate_mhs=0.0,
total_shares=miner_state.shares_submitted,
accepted_shares=miner_state.shares_accepted,
rejected_shares=miner_state.shares_submitted - miner_state.shares_accepted,
last_block_time=None,
mining_since=miner_state.started_at,
)
miner_filter = Block.miner_address == wallet.address
# Count blocks mined by this user
blocks_count_result = await db.execute(
select(func.count(Block.id)).filter(miner_filter)
)
blocks_mined = blocks_count_result.scalar() or 0
# Sum rewards earned
rewards_result = await db.execute(
select(func.sum(Block.reward)).filter(miner_filter)
)
roadcoins_earned = rewards_result.scalar() or 0.0
# Get last block mined
last_block_result = await db.execute(
select(Block)
.filter(miner_filter)
.order_by(desc(Block.timestamp))
.limit(1)
)
last_block = last_block_result.scalar_one_or_none()
# Calculate average hashrate (simulated based on blocks mined)
average_hashrate = 0.0
if blocks_mined > 0:
# Rough estimate: difficulty 4 = ~40 MH/s average
average_hashrate = 40.0 + (blocks_mined * 0.5)
return MinerStats(
blocks_mined=blocks_mined,
roadcoins_earned=float(roadcoins_earned),
current_hashrate_mhs=miner_state.hashrate_mhs if miner_state.is_mining else 0.0,
average_hashrate_mhs=round(average_hashrate, 2),
total_shares=miner_state.shares_submitted,
accepted_shares=miner_state.shares_accepted,
rejected_shares=miner_state.shares_submitted - miner_state.shares_accepted,
last_block_time=last_block.timestamp if last_block else None,
mining_since=miner_state.started_at,
)
@router.get("/blocks", response_model=List[RecentBlock])
async def get_recent_blocks(
limit: int = 10,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Get recently mined blocks by this user."""
# Get user's wallet
wallet_result = await db.execute(
select(Wallet).filter(Wallet.user_id == current_user.id)
)
wallet = wallet_result.scalar_one_or_none()
if not wallet:
return []
# Get recent blocks
blocks_result = await db.execute(
select(Block)
.filter(Block.miner_address == wallet.address)
.order_by(desc(Block.timestamp))
.limit(limit)
)
blocks = blocks_result.scalars().all()
return [
RecentBlock(
block_index=block.index,
block_hash=block.hash,
reward=block.reward,
timestamp=block.timestamp,
difficulty=block.difficulty,
)
for block in blocks
]
@router.post("/control")
async def control_miner(
control: MinerControl,
background_tasks: BackgroundTasks,
current_user: User = Depends(get_current_user),
):
"""Control miner operations (start/stop/restart)."""
if control.action == "start":
if miner_state.is_mining:
raise HTTPException(status_code=400, detail="Miner is already running")
miner_state.is_mining = True
miner_state.started_at = utc_now()
miner_state.hashrate_mhs = random.uniform(38.0, 45.0) # Simulate hashrate
if control.pool_url:
miner_state.pool_url = control.pool_url
if control.worker_id:
miner_state.worker_id = control.worker_id
# Start background mining simulation
background_tasks.add_task(simulate_mining)
return {"message": "Miner started successfully", "status": "running"}
elif control.action == "stop":
if not miner_state.is_mining:
raise HTTPException(status_code=400, detail="Miner is not running")
miner_state.is_mining = False
miner_state.hashrate_mhs = 0.0
return {"message": "Miner stopped successfully", "status": "stopped"}
elif control.action == "restart":
miner_state.is_mining = False
await asyncio.sleep(1)
miner_state.is_mining = True
miner_state.started_at = utc_now()
miner_state.hashrate_mhs = random.uniform(38.0, 45.0)
background_tasks.add_task(simulate_mining)
return {"message": "Miner restarted successfully", "status": "running"}
else:
raise HTTPException(status_code=400, detail=f"Invalid action: {control.action}")
async def simulate_mining():
"""Background task to simulate mining activity."""
while miner_state.is_mining:
# Simulate share submission every 10-30 seconds
await asyncio.sleep(random.uniform(10, 30))
if not miner_state.is_mining:
break
miner_state.shares_submitted += 1
# 95% acceptance rate
if random.random() < 0.95:
miner_state.shares_accepted += 1
# Vary hashrate slightly
miner_state.hashrate_mhs = random.uniform(38.0, 45.0)
# Vary temperature
miner_state.temperature_celsius = random.uniform(60.0, 75.0)
@router.get("/pool/info")
async def get_pool_info(
current_user: User = Depends(get_current_user),
):
"""Get mining pool information."""
return {
"pool_url": miner_state.pool_url,
"pool_name": "RoadCoin Mining Pool",
"pool_hashrate": "2.4 GH/s",
"connected_miners": 142,
"pool_fee": "1%",
"min_payout": 10.0,
"payment_interval_hours": 24,
"last_block_found": (utc_now() - timedelta(minutes=random.randint(5, 120))).isoformat(),
}