chore: remove configuration files and enhance blockchain explorer with advanced search, analytics, and export features
- Delete .aitbc.yaml.example CLI configuration template - Delete .lycheeignore link checker exclusion rules - Delete .nvmrc Node.js version specification - Add advanced search panel with filters for address, amount range, transaction type, time range, and validator - Add analytics dashboard with transaction volume, active addresses, and block time metrics - Add Chart.js integration
This commit is contained in:
259
cli/examples/miner.py
Executable file
259
cli/examples/miner.py
Executable file
@@ -0,0 +1,259 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
AITBC Miner CLI Tool - Register, poll for jobs, and submit results
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import httpx
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
# Configuration
|
||||
DEFAULT_COORDINATOR = "http://localhost:8000"
|
||||
DEFAULT_API_KEY = "${MINER_API_KEY}"
|
||||
DEFAULT_MINER_ID = "cli-miner"
|
||||
|
||||
class AITBCMiner:
|
||||
def __init__(self, coordinator_url: str, api_key: str, miner_id: str):
|
||||
self.coordinator_url = coordinator_url
|
||||
self.api_key = api_key
|
||||
self.miner_id = miner_id
|
||||
self.client = httpx.Client()
|
||||
|
||||
def register(self, capabilities: dict) -> bool:
|
||||
"""Register miner with coordinator"""
|
||||
try:
|
||||
response = self.client.post(
|
||||
f"{self.coordinator_url}/v1/miners/register?miner_id={self.miner_id}",
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"X-Api-Key": self.api_key
|
||||
},
|
||||
json={"capabilities": capabilities}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
print(f"✅ Miner {self.miner_id} registered successfully")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ Registration failed: {response.status_code}")
|
||||
print(f" Response: {response.text}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return False
|
||||
|
||||
def poll_job(self, max_wait: int = 5) -> Optional[dict]:
|
||||
"""Poll for available jobs"""
|
||||
try:
|
||||
response = self.client.post(
|
||||
f"{self.coordinator_url}/v1/miners/poll",
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"X-Api-Key": self.api_key
|
||||
},
|
||||
json={"max_wait_seconds": max_wait}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
elif response.status_code == 204:
|
||||
return None
|
||||
else:
|
||||
print(f"❌ Poll failed: {response.status_code}")
|
||||
print(f" Response: {response.text}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return None
|
||||
|
||||
def submit_result(self, job_id: str, result: dict, metrics: dict = None) -> bool:
|
||||
"""Submit job result"""
|
||||
payload = {
|
||||
"result": result
|
||||
}
|
||||
if metrics:
|
||||
payload["metrics"] = metrics
|
||||
|
||||
try:
|
||||
response = self.client.post(
|
||||
f"{self.coordinator_url}/v1/miners/{job_id}/result",
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"X-Api-Key": self.api_key
|
||||
},
|
||||
json=payload
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
print(f"✅ Result submitted for job {job_id}")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ Submit failed: {response.status_code}")
|
||||
print(f" Response: {response.text}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return False
|
||||
|
||||
def send_heartbeat(self) -> bool:
|
||||
"""Send heartbeat to coordinator"""
|
||||
heartbeat_data = {
|
||||
"status": "ONLINE",
|
||||
"inflight": 0,
|
||||
"metadata": {
|
||||
"last_seen": datetime.utcnow().isoformat(),
|
||||
"gpu_utilization": 75,
|
||||
"gpu_memory_used": 8000,
|
||||
"gpu_temperature": 65
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
response = self.client.post(
|
||||
f"{self.coordinator_url}/v1/miners/heartbeat?miner_id={self.miner_id}",
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"X-Api-Key": self.api_key
|
||||
},
|
||||
json=heartbeat_data
|
||||
)
|
||||
|
||||
return response.status_code == 200
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Heartbeat error: {e}")
|
||||
return False
|
||||
|
||||
def mine_continuous(self, max_jobs: int = None, simulate_work: bool = True):
|
||||
"""Continuously mine jobs"""
|
||||
print(f"⛏️ Starting continuous mining...")
|
||||
print(f" Miner ID: {self.miner_id}")
|
||||
print(f" Max jobs: {max_jobs or 'unlimited'}")
|
||||
print()
|
||||
|
||||
jobs_completed = 0
|
||||
|
||||
try:
|
||||
while max_jobs is None or jobs_completed < max_jobs:
|
||||
# Poll for job
|
||||
print("🔍 Polling for jobs...")
|
||||
job = self.poll_job()
|
||||
|
||||
if job:
|
||||
print(f"✅ Got job: {job['job_id']}")
|
||||
print(f" Type: {job['payload'].get('type', 'unknown')}")
|
||||
|
||||
if simulate_work:
|
||||
print("⚙️ Processing job...")
|
||||
time.sleep(2) # Simulate work
|
||||
|
||||
# Submit result
|
||||
result = {
|
||||
"status": "completed",
|
||||
"output": f"Job {job['job_id']} processed by {self.miner_id}",
|
||||
"execution_time_ms": 2000,
|
||||
"miner_id": self.miner_id
|
||||
}
|
||||
|
||||
metrics = {
|
||||
"compute_time": 2.0,
|
||||
"energy_used": 0.1,
|
||||
"aitbc_earned": 10.0
|
||||
}
|
||||
|
||||
if self.submit_result(job['job_id'], result, metrics):
|
||||
jobs_completed += 1
|
||||
print(f"💰 Earned 10 AITBC!")
|
||||
print(f" Total jobs completed: {jobs_completed}")
|
||||
|
||||
# Check if this job is now a block with proposer
|
||||
print("🔍 Checking block status...")
|
||||
time.sleep(1)
|
||||
else:
|
||||
print("💤 No jobs available, sending heartbeat...")
|
||||
self.send_heartbeat()
|
||||
|
||||
print("-" * 50)
|
||||
time.sleep(3) # Wait before next poll
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n⏹️ Mining stopped by user")
|
||||
print(f" Total jobs completed: {jobs_completed}")
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="AITBC Miner CLI")
|
||||
parser.add_argument("--url", default=DEFAULT_COORDINATOR, help="Coordinator URL")
|
||||
parser.add_argument("--api-key", default=DEFAULT_API_KEY, help="API key")
|
||||
parser.add_argument("--miner-id", default=DEFAULT_MINER_ID, help="Miner ID")
|
||||
|
||||
subparsers = parser.add_subparsers(dest="command", help="Commands")
|
||||
|
||||
# Register command
|
||||
register_parser = subparsers.add_parser("register", help="Register miner")
|
||||
register_parser.add_argument("--gpu", default="RTX 4060 Ti", help="GPU model")
|
||||
register_parser.add_argument("--memory", type=int, default=16, help="GPU memory GB")
|
||||
|
||||
# Poll command
|
||||
poll_parser = subparsers.add_parser("poll", help="Poll for a job")
|
||||
poll_parser.add_argument("--wait", type=int, default=5, help="Max wait seconds")
|
||||
|
||||
# Mine command
|
||||
mine_parser = subparsers.add_parser("mine", help="Mine continuously")
|
||||
mine_parser.add_argument("--jobs", type=int, help="Max jobs to complete")
|
||||
mine_parser.add_argument("--no-simulate", action="store_true", help="Don't simulate work")
|
||||
|
||||
# Heartbeat command
|
||||
heartbeat_parser = subparsers.add_parser("heartbeat", help="Send heartbeat")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.command:
|
||||
parser.print_help()
|
||||
return
|
||||
|
||||
miner = AITBCMiner(args.url, args.api_key, args.miner_id)
|
||||
|
||||
if args.command == "register":
|
||||
capabilities = {
|
||||
"gpu": {
|
||||
"model": args.gpu,
|
||||
"memory_gb": args.memory,
|
||||
"cuda_version": "12.4"
|
||||
},
|
||||
"compute": {
|
||||
"type": "GPU",
|
||||
"platform": "CUDA",
|
||||
"supported_tasks": ["inference", "training"],
|
||||
"max_concurrent_jobs": 1
|
||||
}
|
||||
}
|
||||
miner.register(capabilities)
|
||||
|
||||
elif args.command == "poll":
|
||||
print(f"🔍 Polling for jobs (max wait: {args.wait}s)...")
|
||||
job = miner.poll_job(args.wait)
|
||||
|
||||
if job:
|
||||
print(f"✅ Received job:")
|
||||
print(json.dumps(job, indent=2))
|
||||
else:
|
||||
print("💤 No jobs available")
|
||||
|
||||
elif args.command == "mine":
|
||||
miner.mine_continuous(args.jobs, not args.no_simulate)
|
||||
|
||||
elif args.command == "heartbeat":
|
||||
if miner.send_heartbeat():
|
||||
print("💓 Heartbeat sent successfully")
|
||||
else:
|
||||
print("❌ Heartbeat failed")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user