|
|
import os |
|
|
import json |
|
|
import time |
|
|
import threading |
|
|
from fastapi import FastAPI, HTTPException, BackgroundTasks |
|
|
from fastapi.middleware.cors import CORSMiddleware |
|
|
from fastapi.responses import JSONResponse |
|
|
import uvicorn |
|
|
from typing import Dict |
|
|
from datetime import datetime |
|
|
|
|
|
|
|
|
from parallel_miner_v3 import ParallelMiner, MiningCore, HashUnit |
|
|
|
|
|
|
|
|
app = FastAPI( |
|
|
title="Bitcoin Mining API", |
|
|
description="API endpoints for Bitcoin mining operations using electron-speed SHA-256", |
|
|
version="3.0.0" |
|
|
) |
|
|
|
|
|
|
|
|
app.add_middleware( |
|
|
CORSMiddleware, |
|
|
allow_origins=["*"], |
|
|
allow_credentials=True, |
|
|
allow_methods=["*"], |
|
|
allow_headers=["*"], |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
STATS_FILE = "mining_stats_history.json" |
|
|
|
|
|
|
|
|
miner_instance = None |
|
|
mining_thread = None |
|
|
mining_stats = { |
|
|
"is_mining": False, |
|
|
"total_hashes": 0, |
|
|
"blocks_found": 0, |
|
|
"hash_rate": 0.0, |
|
|
"best_hash": None, |
|
|
"best_hash_difficulty": 0, |
|
|
"start_time": None, |
|
|
"total_runtime": 0, |
|
|
"session_count": 0, |
|
|
"best_session_hashrate": 0, |
|
|
"all_time_total_hashes": 0, |
|
|
"logs": [] |
|
|
} |
|
|
|
|
|
@app.get("/") |
|
|
async def root(): |
|
|
"""API root endpoint""" |
|
|
return { |
|
|
"message": "MP4 Processing API", |
|
|
"version": "1.0.0", |
|
|
"status": "running" |
|
|
} |
|
|
|
|
|
|
|
|
def save_mining_stats(): |
|
|
"""Save mining statistics to file""" |
|
|
try: |
|
|
if os.path.exists(STATS_FILE): |
|
|
with open(STATS_FILE, 'r') as f: |
|
|
historical_stats = json.load(f) |
|
|
else: |
|
|
historical_stats = {"sessions": []} |
|
|
|
|
|
|
|
|
end_time = time.time() |
|
|
elapsed = end_time - mining_stats["start_time"] if mining_stats["start_time"] else 0 |
|
|
hash_rate = mining_stats["total_hashes"] / elapsed if elapsed > 0 else 0 |
|
|
|
|
|
session_stats = { |
|
|
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), |
|
|
"duration": f"{elapsed:.2f}s", |
|
|
"total_hashes": mining_stats["total_hashes"], |
|
|
"avg_hash_rate": f"{hash_rate/1000:.2f} KH/s", |
|
|
"blocks_found": mining_stats["blocks_found"], |
|
|
"best_hash": mining_stats["best_hash"].hex() if mining_stats["best_hash"] else None, |
|
|
"best_hash_difficulty": mining_stats["best_hash_difficulty"] |
|
|
} |
|
|
|
|
|
historical_stats["sessions"].append(session_stats) |
|
|
historical_stats["total_runtime"] = mining_stats["total_runtime"] + elapsed |
|
|
historical_stats["total_hashes"] = mining_stats["all_time_total_hashes"] + mining_stats["total_hashes"] |
|
|
historical_stats["total_blocks"] = sum(s["blocks_found"] for s in historical_stats["sessions"]) |
|
|
historical_stats["best_session_hashrate"] = max( |
|
|
mining_stats["best_session_hashrate"], |
|
|
hash_rate/1000 |
|
|
) |
|
|
|
|
|
with open(STATS_FILE, 'w') as f: |
|
|
json.dump(historical_stats, f, indent=2) |
|
|
|
|
|
except Exception as e: |
|
|
log_mining(f"Error saving mining stats: {str(e)}") |
|
|
|
|
|
def log_mining(message): |
|
|
"""Add a mining log message with timestamp""" |
|
|
timestamp = datetime.now().strftime("%H:%M:%S") |
|
|
log_entry = f"[{timestamp}] {message}" |
|
|
mining_stats["logs"].append(log_entry) |
|
|
|
|
|
|
|
|
if len(mining_stats["logs"]) > 100: |
|
|
mining_stats["logs"] = mining_stats["logs"][-100:] |
|
|
|
|
|
print(log_entry) |
|
|
|
|
|
@app.on_event("startup") |
|
|
async def startup_event(): |
|
|
"""Initialize mining components and start mining on startup""" |
|
|
global miner_instance, mining_thread |
|
|
if not (miner_instance and hasattr(miner_instance, 'mining') and miner_instance.mining): |
|
|
log_mining("π Initializing Bitcoin mining components...") |
|
|
try: |
|
|
miner_instance = ParallelMiner(num_cores=5) |
|
|
miner_instance.mining = True |
|
|
|
|
|
|
|
|
mining_thread = threading.Thread( |
|
|
target=miner_instance.start_mining, |
|
|
kwargs={"duration": None} |
|
|
) |
|
|
mining_thread.daemon = True |
|
|
mining_thread.start() |
|
|
|
|
|
|
|
|
mining_stats["is_mining"] = True |
|
|
mining_stats["start_time"] = time.time() |
|
|
mining_stats["total_hashes"] = 0 |
|
|
mining_stats["blocks_found"] = 0 |
|
|
mining_stats["best_hash"] = None |
|
|
|
|
|
log_mining(f"Started mining automatically with {len(miner_instance.cores)} cores") |
|
|
except Exception as e: |
|
|
log_mining(f"Error starting mining: {str(e)}") |
|
|
raise |
|
|
|
|
|
from fastapi.staticfiles import StaticFiles |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/mining/status") |
|
|
async def get_mining_status(): |
|
|
"""Get current mining status and statistics""" |
|
|
if not miner_instance: |
|
|
return {"error": "Mining system not initialized"} |
|
|
|
|
|
current_time = time.time() |
|
|
if mining_stats["start_time"]: |
|
|
elapsed = current_time - mining_stats["start_time"] |
|
|
else: |
|
|
elapsed = 0 |
|
|
|
|
|
|
|
|
mining_stats["total_hashes"] = miner_instance.total_hashes |
|
|
mining_stats["blocks_found"] = miner_instance.blocks_found |
|
|
mining_stats["best_hash"] = miner_instance.best_hash |
|
|
mining_stats["best_hash_difficulty"] = miner_instance.best_hash_difficulty |
|
|
|
|
|
return { |
|
|
"is_mining": mining_stats["is_mining"], |
|
|
"total_hashes": miner_instance.total_hashes, |
|
|
"hash_rate": f"{miner_instance.current_hashrate/1000:.2f} KH/s", |
|
|
"blocks_found": miner_instance.blocks_found, |
|
|
"best_hash": miner_instance.best_hash.hex() if miner_instance.best_hash else None, |
|
|
"difficulty": { |
|
|
"network": miner_instance.network_difficulty, |
|
|
"best_achieved": miner_instance.best_hash_difficulty, |
|
|
"percent_to_network": f"{(miner_instance.best_hash_difficulty / miner_instance.network_difficulty * 100):.4f}%" if miner_instance.network_difficulty > 0 else "0%" |
|
|
}, |
|
|
"uptime": f"{elapsed:.2f}s" if mining_stats["start_time"] else "0s", |
|
|
"cores_active": len(miner_instance.cores) if miner_instance else 0, |
|
|
"units_per_core": len(miner_instance.cores[0].units) if miner_instance and miner_instance.cores else 0, |
|
|
"logs": mining_stats["logs"][-10:] |
|
|
} |
|
|
|
|
|
@app.get("/mining/performance") |
|
|
async def get_mining_performance(): |
|
|
"""Get detailed mining performance metrics""" |
|
|
global miner_instance, mining_stats |
|
|
|
|
|
if not miner_instance: |
|
|
return {"error": "Mining system not initialized"} |
|
|
|
|
|
current_time = time.time() |
|
|
if mining_stats["start_time"]: |
|
|
elapsed = current_time - mining_stats["start_time"] |
|
|
hash_rate = mining_stats["total_hashes"] / elapsed if elapsed > 0 else 0 |
|
|
hashes_per_core = mining_stats["total_hashes"] / len(miner_instance.cores) if miner_instance.cores else 0 |
|
|
else: |
|
|
elapsed = 0 |
|
|
hash_rate = 0 |
|
|
hashes_per_core = 0 |
|
|
|
|
|
core_stats = [] |
|
|
if miner_instance and miner_instance.cores: |
|
|
for i, core in enumerate(miner_instance.cores): |
|
|
core_stats.append({ |
|
|
"core_id": i, |
|
|
"active_units": len(core.units), |
|
|
"status": "active" if mining_stats["is_mining"] else "idle" |
|
|
}) |
|
|
|
|
|
return { |
|
|
"overall_performance": { |
|
|
"hash_rate": f"{miner_instance.current_hashrate/1000:.2f} KH/s", |
|
|
"total_hashes": miner_instance.total_hashes, |
|
|
"blocks_found": miner_instance.blocks_found, |
|
|
"uptime": f"{elapsed:.2f}s", |
|
|
"hashes_per_core": f"{(miner_instance.total_hashes/len(miner_instance.cores))/1000:.2f}K" |
|
|
}, |
|
|
"core_utilization": { |
|
|
"total_cores": len(miner_instance.cores), |
|
|
"active_cores": len([c for c in core_stats if c["status"] == "active"]), |
|
|
"cores": core_stats |
|
|
}, |
|
|
"memory_usage": { |
|
|
"core_memory": len(miner_instance.cores) * 1024 * 1024, |
|
|
"total_allocated": len(miner_instance.cores) * len(miner_instance.cores[0].units) * 1024 if miner_instance.cores else 0 |
|
|
} |
|
|
} |
|
|
|
|
|
from fastapi.encoders import jsonable_encoder |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.post("/mining/stop") |
|
|
async def stop_mining(): |
|
|
"""Stop Bitcoin mining operations""" |
|
|
global miner_instance, mining_stats |
|
|
|
|
|
if not mining_stats["is_mining"]: |
|
|
return {"message": "Mining is not currently running", "status": "not_running"} |
|
|
|
|
|
if miner_instance: |
|
|
miner_instance.mining = False |
|
|
mining_stats["is_mining"] = False |
|
|
|
|
|
|
|
|
end_time = time.time() |
|
|
elapsed = end_time - mining_stats["start_time"] |
|
|
hash_rate = miner_instance.current_hashrate |
|
|
|
|
|
|
|
|
mining_stats["total_runtime"] += elapsed |
|
|
mining_stats["all_time_total_hashes"] += miner_instance.total_hashes |
|
|
mining_stats["best_session_hashrate"] = max( |
|
|
mining_stats["best_session_hashrate"], |
|
|
hash_rate/1000 |
|
|
) |
|
|
|
|
|
|
|
|
mining_stats["total_hashes"] = miner_instance.total_hashes |
|
|
mining_stats["blocks_found"] = miner_instance.blocks_found |
|
|
mining_stats["best_hash"] = miner_instance.best_hash |
|
|
mining_stats["best_hash_difficulty"] = miner_instance.best_hash_difficulty |
|
|
|
|
|
|
|
|
save_mining_stats() |
|
|
|
|
|
|
|
|
log_mining("=== Mining Session Completed ===") |
|
|
log_mining(f"Session Duration: {elapsed:.2f}s") |
|
|
log_mining(f"Total Hashes: {mining_stats['total_hashes']:,}") |
|
|
log_mining(f"Average Hash Rate: {hash_rate/1000:.2f} KH/s") |
|
|
log_mining(f"Blocks Found: {mining_stats['blocks_found']}") |
|
|
log_mining(f"Best Hash: {mining_stats['best_hash'].hex() if mining_stats['best_hash'] else 'None'}") |
|
|
log_mining(f"Best Hash Difficulty: {mining_stats['best_hash_difficulty']}") |
|
|
log_mining("\n=== All-Time Statistics ===") |
|
|
log_mining(f"Total Runtime: {mining_stats['total_runtime']:.2f}s") |
|
|
log_mining(f"Total Hashes: {mining_stats['all_time_total_hashes']:,}") |
|
|
log_mining(f"Total Blocks Found: {mining_stats['blocks_found']}") |
|
|
log_mining(f"Best Session Hash Rate: {mining_stats['best_session_hashrate']:.2f} KH/s") |
|
|
|
|
|
return { |
|
|
"message": "Mining stopped successfully", |
|
|
"status": "stopped", |
|
|
"session_stats": { |
|
|
"duration": f"{elapsed:.2f}s", |
|
|
"total_hashes": mining_stats["total_hashes"], |
|
|
"avg_hash_rate": f"{hash_rate/1000:.2f} KH/s", |
|
|
"blocks_found": mining_stats["blocks_found"], |
|
|
"best_hash": mining_stats["best_hash"].hex() if mining_stats["best_hash"] else None, |
|
|
"best_hash_difficulty": mining_stats["best_hash_difficulty"] |
|
|
}, |
|
|
"all_time_stats": { |
|
|
"total_runtime": f"{mining_stats['total_runtime']:.2f}s", |
|
|
"total_hashes": mining_stats["all_time_total_hashes"], |
|
|
"total_blocks": mining_stats["blocks_found"], |
|
|
"best_session_hashrate": f"{mining_stats['best_session_hashrate']:.2f} KH/s" |
|
|
} |
|
|
} |
|
|
|
|
|
return {"message": "Mining instance not found", "status": "error"} |
|
|
|
|
|
|
|
|
@app.get("/mining/history") |
|
|
async def get_mining_history(): |
|
|
"""Get historical mining statistics""" |
|
|
try: |
|
|
if os.path.exists(STATS_FILE): |
|
|
with open(STATS_FILE, 'r') as f: |
|
|
historical_stats = json.load(f) |
|
|
return historical_stats |
|
|
else: |
|
|
return { |
|
|
"sessions": [], |
|
|
"total_runtime": 0, |
|
|
"total_hashes": 0, |
|
|
"total_blocks": 0, |
|
|
"best_session_hashrate": 0 |
|
|
} |
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=500, detail=f"Error reading mining history: {str(e)}") |
|
|
|
|
|
def handle_shutdown(): |
|
|
"""Handle graceful shutdown and save statistics""" |
|
|
if mining_stats["is_mining"] and miner_instance: |
|
|
log_mining("\nπ Server shutdown detected - Saving final mining statistics...") |
|
|
miner_instance.mining = False |
|
|
mining_stats["is_mining"] = False |
|
|
|
|
|
|
|
|
end_time = time.time() |
|
|
elapsed = end_time - mining_stats["start_time"] |
|
|
hash_rate = mining_stats["total_hashes"] / elapsed if elapsed > 0 else 0 |
|
|
|
|
|
|
|
|
mining_stats["total_runtime"] += elapsed |
|
|
mining_stats["all_time_total_hashes"] += mining_stats["total_hashes"] |
|
|
mining_stats["best_session_hashrate"] = max( |
|
|
mining_stats["best_session_hashrate"], |
|
|
hash_rate/1000 |
|
|
) |
|
|
|
|
|
|
|
|
log_mining("=== Final Mining Statistics ===") |
|
|
log_mining(f"Session Duration: {elapsed:.2f}s") |
|
|
log_mining(f"Total Hashes: {mining_stats['total_hashes']:,}") |
|
|
log_mining(f"Average Hash Rate: {hash_rate/1000:.2f} KH/s") |
|
|
log_mining(f"Blocks Found: {mining_stats['blocks_found']}") |
|
|
if mining_stats["best_hash"]: |
|
|
log_mining(f"Best Hash: {mining_stats['best_hash'].hex()}") |
|
|
|
|
|
|
|
|
save_mining_stats() |
|
|
log_mining("Statistics saved successfully") |
|
|
log_mining("Server shutting down... Goodbye! π") |
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
|
|
print("API Documentation will be available at: http://localhost:8000/docs") |
|
|
print("API Root endpoint: http://localhost:8000/") |
|
|
|
|
|
try: |
|
|
uvicorn.run( |
|
|
app, |
|
|
host="0.0.0.0", |
|
|
port=8000, |
|
|
log_level="info", |
|
|
reload=False |
|
|
) |
|
|
except KeyboardInterrupt: |
|
|
handle_shutdown() |
|
|
except Exception as e: |
|
|
log_mining(f"Error during server operation: {str(e)}") |
|
|
handle_shutdown() |
|
|
raise |
|
|
|
|
|
|