```
chore: enhance .gitignore and remove obsolete documentation files - Reorganize .gitignore with categorized sections for better maintainability - Add comprehensive ignore patterns for Python, Node.js, databases, logs, and build artifacts - Add project-specific ignore rules for coordinator, explorer, and deployment files - Remove outdated documentation: BITCOIN-WALLET-SETUP.md, LOCAL_ASSETS_SUMMARY.md, README-CONTAINER-DEPLOYMENT.md, README-DOMAIN-DEPLOYMENT.md ```
This commit is contained in:
155
cli/README.md
Normal file
155
cli/README.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# AITBC CLI Tools
|
||||
|
||||
Command-line tools for interacting with the AITBC network without using the web frontend.
|
||||
|
||||
## Tools
|
||||
|
||||
### 1. Client CLI (`client.py`)
|
||||
Submit jobs and check their status.
|
||||
|
||||
```bash
|
||||
# Submit an inference job
|
||||
python3 client.py submit inference --model llama-2-7b --prompt "What is AITBC?"
|
||||
|
||||
# Check job status
|
||||
python3 client.py status <job_id>
|
||||
|
||||
# List recent blocks
|
||||
python3 client.py blocks --limit 5
|
||||
|
||||
# Submit a quick demo job
|
||||
python3 client.py demo
|
||||
```
|
||||
|
||||
### 2. Miner CLI (`miner.py`)
|
||||
Register as a miner, poll for jobs, and earn AITBC.
|
||||
|
||||
```bash
|
||||
# Register as a miner
|
||||
python3 miner.py register --gpu "RTX 4060 Ti" --memory 16
|
||||
|
||||
# Poll for a single job
|
||||
python3 miner.py poll --wait 5
|
||||
|
||||
# Mine continuously (process jobs as they come)
|
||||
python3 miner.py mine --jobs 10
|
||||
|
||||
# Send heartbeat to coordinator
|
||||
python3 miner.py heartbeat
|
||||
```
|
||||
|
||||
### 3. Wallet CLI (`wallet.py`)
|
||||
Track your AITBC earnings and manage your wallet.
|
||||
|
||||
```bash
|
||||
# Check balance
|
||||
python3 wallet.py balance
|
||||
|
||||
# Show transaction history
|
||||
python3 wallet.py history --limit 10
|
||||
|
||||
# Add earnings (after completing a job)
|
||||
python3 wallet.py earn 10.0 --job abc123 --desc "Inference task"
|
||||
|
||||
# Spend AITBC
|
||||
python3 wallet.py spend 5.0 "Coffee break"
|
||||
|
||||
# Show wallet address
|
||||
python3 wallet.py address
|
||||
```
|
||||
|
||||
## GPU Testing
|
||||
|
||||
Before mining, verify your GPU is accessible:
|
||||
|
||||
```bash
|
||||
# Quick GPU check
|
||||
python3 test_gpu_access.py
|
||||
|
||||
# Comprehensive GPU test
|
||||
python3 gpu_test.py
|
||||
|
||||
# Test miner with GPU
|
||||
python3 miner_gpu_test.py --full
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. **Start the SSH tunnel to remote server** (if not already running):
|
||||
```bash
|
||||
cd /home/oib/windsurf/aitbc
|
||||
./scripts/start_remote_tunnel.sh
|
||||
```
|
||||
|
||||
2. **Run the complete workflow test**:
|
||||
```bash
|
||||
cd /home/oib/windsurf/aitbc/cli
|
||||
python3 test_workflow.py
|
||||
```
|
||||
|
||||
3. **Start mining continuously**:
|
||||
```bash
|
||||
# Terminal 1: Start mining
|
||||
python3 miner.py mine
|
||||
|
||||
# Terminal 2: Submit jobs
|
||||
python3 client.py submit training --model "stable-diffusion"
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
All tools default to connecting to `http://localhost:8001` (the remote server via SSH tunnel). You can override this:
|
||||
|
||||
```bash
|
||||
python3 client.py --url http://localhost:8000 --api-key your_key submit inference
|
||||
```
|
||||
|
||||
Default credentials:
|
||||
- Client API Key: `REDACTED_CLIENT_KEY`
|
||||
- Miner API Key: `REDACTED_MINER_KEY`
|
||||
|
||||
## Examples
|
||||
|
||||
### Submit and Process a Job
|
||||
|
||||
```bash
|
||||
# 1. Submit a job
|
||||
JOB_ID=$(python3 client.py submit inference --prompt "Test" | grep "Job ID" | cut -d' ' -f4)
|
||||
|
||||
# 2. In another terminal, mine it
|
||||
python3 miner.py poll
|
||||
|
||||
# 3. Check the result
|
||||
python3 client.py status $JOB_ID
|
||||
|
||||
# 4. See it in the blockchain
|
||||
python3 client.py blocks
|
||||
```
|
||||
|
||||
### Continuous Mining
|
||||
|
||||
```bash
|
||||
# Register and start mining
|
||||
python3 miner.py register
|
||||
python3 miner.py mine --jobs 5
|
||||
|
||||
# In another terminal, submit multiple jobs
|
||||
for i in {1..5}; do
|
||||
python3 client.py submit inference --prompt "Job $i"
|
||||
sleep 1
|
||||
done
|
||||
```
|
||||
|
||||
## Tips
|
||||
|
||||
- The wallet is stored in `~/.aitbc_wallet.json`
|
||||
- Jobs appear as blocks immediately when created
|
||||
- The proposer is assigned when a miner polls for the job
|
||||
- Use `--help` with any command to see all options
|
||||
- Mining earnings are added manually for now (will be automatic in production)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- If you get "No jobs available", make sure a job was submitted recently
|
||||
- If registration fails, check the coordinator is running and API key is correct
|
||||
- If the tunnel is down, restart it with `./scripts/start_remote_tunnel.sh`
|
||||
288
cli/client.py
Executable file
288
cli/client.py
Executable file
@@ -0,0 +1,288 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
AITBC Client CLI Tool - Submit jobs and check status
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import httpx
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
# Configuration
|
||||
DEFAULT_COORDINATOR = "http://127.0.0.1:8000"
|
||||
DEFAULT_API_KEY = "REDACTED_CLIENT_KEY"
|
||||
|
||||
class AITBCClient:
|
||||
def __init__(self, coordinator_url: str, api_key: str):
|
||||
self.coordinator_url = coordinator_url
|
||||
self.api_key = api_key
|
||||
self.client = httpx.Client()
|
||||
|
||||
def submit_job(self, job_type: str, task_data: dict, ttl: int = 900) -> Optional[str]:
|
||||
"""Submit a job to the coordinator"""
|
||||
job_payload = {
|
||||
"payload": {
|
||||
"type": job_type,
|
||||
**task_data
|
||||
},
|
||||
"ttl_seconds": ttl
|
||||
}
|
||||
|
||||
try:
|
||||
response = self.client.post(
|
||||
f"{self.coordinator_url}/v1/jobs",
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"X-Api-Key": self.api_key
|
||||
},
|
||||
json=job_payload
|
||||
)
|
||||
|
||||
if response.status_code == 201:
|
||||
job = response.json()
|
||||
return job['job_id']
|
||||
else:
|
||||
print(f"❌ Error submitting job: {response.status_code}")
|
||||
print(f" Response: {response.text}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return None
|
||||
|
||||
def list_transactions(self, limit: int = 10) -> Optional[list]:
|
||||
"""List recent transactions"""
|
||||
try:
|
||||
response = self.client.get(
|
||||
f"{self.coordinator_url}/v1/explorer/transactions",
|
||||
params={"limit": limit}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
transactions = response.json()
|
||||
return transactions.get('items', [])[:limit]
|
||||
else:
|
||||
print(f"❌ Error listing transactions: {response.status_code}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return None
|
||||
|
||||
def list_receipts(self, limit: int = 10, job_id: Optional[str] = None) -> Optional[list]:
|
||||
"""List recent receipts"""
|
||||
params = {"limit": limit}
|
||||
if job_id:
|
||||
params["job_id"] = job_id
|
||||
try:
|
||||
response = self.client.get(
|
||||
f"{self.coordinator_url}/v1/explorer/receipts",
|
||||
params=params
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
receipts = response.json()
|
||||
return receipts.get('items', [])[:limit]
|
||||
else:
|
||||
print(f"❌ Error listing receipts: {response.status_code}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return None
|
||||
|
||||
def get_job_status(self, job_id: str) -> Optional[dict]:
|
||||
"""Get job status"""
|
||||
try:
|
||||
response = self.client.get(
|
||||
f"{self.coordinator_url}/v1/jobs/{job_id}",
|
||||
headers={"X-Api-Key": self.api_key}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
print(f"❌ Error getting status: {response.status_code}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return None
|
||||
|
||||
def list_blocks(self, limit: int = 10) -> Optional[list]:
|
||||
"""List recent blocks"""
|
||||
try:
|
||||
response = self.client.get(f"{self.coordinator_url}/v1/explorer/blocks")
|
||||
|
||||
if response.status_code == 200:
|
||||
blocks = response.json()
|
||||
return blocks['items'][:limit]
|
||||
else:
|
||||
print(f"❌ Error listing blocks: {response.status_code}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return None
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="AITBC Client CLI")
|
||||
parser.add_argument("--url", default=DEFAULT_COORDINATOR, help="Coordinator URL")
|
||||
parser.add_argument("--api-key", default=DEFAULT_API_KEY, help="API key")
|
||||
|
||||
subparsers = parser.add_subparsers(dest="command", help="Commands")
|
||||
|
||||
# Submit job command
|
||||
submit_parser = subparsers.add_parser("submit", help="Submit a job")
|
||||
submit_parser.add_argument("type", help="Job type (e.g., inference, training)")
|
||||
submit_parser.add_argument("--task", help="Task description")
|
||||
submit_parser.add_argument("--model", help="Model to use")
|
||||
submit_parser.add_argument("--prompt", help="Prompt for inference")
|
||||
submit_parser.add_argument("--ttl", type=int, default=900, help="TTL in seconds")
|
||||
|
||||
# Status command
|
||||
status_parser = subparsers.add_parser("status", help="Check job status")
|
||||
status_parser.add_argument("job_id", help="Job ID to check")
|
||||
|
||||
# Blocks command
|
||||
blocks_parser = subparsers.add_parser("blocks", help="List recent blocks")
|
||||
blocks_parser.add_argument("--limit", type=int, default=10, help="Number of blocks")
|
||||
|
||||
# Browser command
|
||||
browser_parser = subparsers.add_parser("browser", help="Show latest blocks, transactions, and receipt metrics")
|
||||
browser_parser.add_argument("--block-limit", type=int, default=1, help="Number of blocks")
|
||||
browser_parser.add_argument("--tx-limit", type=int, default=5, help="Number of transactions")
|
||||
browser_parser.add_argument("--receipt-limit", type=int, default=10, help="Number of receipts")
|
||||
browser_parser.add_argument("--job-id", help="Filter receipts by job ID")
|
||||
|
||||
# Quick demo command
|
||||
demo_parser = subparsers.add_parser("demo", help="Submit a demo job")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.command:
|
||||
parser.print_help()
|
||||
return
|
||||
|
||||
client = AITBCClient(args.url, args.api_key)
|
||||
|
||||
if args.command == "submit":
|
||||
task_data = {}
|
||||
if args.task:
|
||||
task_data["task"] = args.task
|
||||
if args.model:
|
||||
task_data["model"] = args.model
|
||||
if args.prompt:
|
||||
task_data["prompt"] = args.prompt
|
||||
task_data["parameters"] = {"prompt": args.prompt}
|
||||
|
||||
print(f"📤 Submitting {args.type} job...")
|
||||
job_id = client.submit_job(args.type, task_data, args.ttl)
|
||||
|
||||
if job_id:
|
||||
print(f"✅ Job submitted successfully!")
|
||||
print(f" Job ID: {job_id}")
|
||||
print(f" Track with: python3 cli/client.py status {job_id}")
|
||||
|
||||
elif args.command == "status":
|
||||
print(f"🔍 Checking status for job {args.job_id}...")
|
||||
status = client.get_job_status(args.job_id)
|
||||
|
||||
if status:
|
||||
print(f"📊 Job Status:")
|
||||
print(f" ID: {status['job_id']}")
|
||||
print(f" State: {status['state']}")
|
||||
print(f" Miner: {status.get('assigned_miner_id', 'None')}")
|
||||
print(f" Created: {status['requested_at']}")
|
||||
if status.get('expires_at'):
|
||||
print(f" Expires: {status['expires_at']}")
|
||||
|
||||
elif args.command == "blocks":
|
||||
print(f"📦 Recent blocks (last {args.limit}):")
|
||||
blocks = client.list_blocks(args.limit)
|
||||
|
||||
if blocks:
|
||||
for i, block in enumerate(blocks, 1):
|
||||
print(f"\n{i}. Height: {block['height']}")
|
||||
print(f" Hash: {block['hash']}")
|
||||
print(f" Time: {block['timestamp']}")
|
||||
print(f" Proposer: {block['proposer']}")
|
||||
|
||||
elif args.command == "browser":
|
||||
blocks = client.list_blocks(args.block_limit) or []
|
||||
transactions = client.list_transactions(args.tx_limit) or []
|
||||
receipts = client.list_receipts(args.receipt_limit, job_id=args.job_id) or []
|
||||
|
||||
print("🧭 Blockchain Browser Snapshot")
|
||||
if blocks:
|
||||
block = blocks[0]
|
||||
tx_count = block.get("txCount", block.get("tx_count"))
|
||||
print("\n🧱 Latest Block")
|
||||
print(f" Height: {block.get('height')}")
|
||||
print(f" Hash: {block.get('hash')}")
|
||||
print(f" Time: {block.get('timestamp')}")
|
||||
print(f" Tx Count: {tx_count}")
|
||||
print(f" Proposer: {block.get('proposer')}")
|
||||
else:
|
||||
print("\n🧱 Latest Block: none found")
|
||||
|
||||
print("\n🧾 Latest Transactions")
|
||||
if not transactions:
|
||||
print(" No transactions found")
|
||||
for tx in transactions:
|
||||
tx_hash = tx.get("hash") or tx.get("tx_hash")
|
||||
from_addr = tx.get("from") or tx.get("from_address")
|
||||
to_addr = tx.get("to") or tx.get("to_address")
|
||||
value = tx.get("value")
|
||||
status = tx.get("status")
|
||||
block_ref = tx.get("block")
|
||||
print(f" - {tx_hash} | block {block_ref} | {status}")
|
||||
print(f" from: {from_addr} -> to: {to_addr} | value: {value}")
|
||||
|
||||
print("\n📈 Receipt Metrics (recent)")
|
||||
if not receipts:
|
||||
print(" No receipts found")
|
||||
else:
|
||||
status_counts = {}
|
||||
total_units = 0.0
|
||||
unit_type = None
|
||||
for receipt in receipts:
|
||||
status_label = receipt.get("status") or receipt.get("state") or "Unknown"
|
||||
status_counts[status_label] = status_counts.get(status_label, 0) + 1
|
||||
payload = receipt.get("payload") or {}
|
||||
units = payload.get("units")
|
||||
if isinstance(units, (int, float)):
|
||||
total_units += float(units)
|
||||
if unit_type is None:
|
||||
unit_type = payload.get("unit_type")
|
||||
|
||||
print(f" Receipts: {len(receipts)}")
|
||||
for status_label, count in status_counts.items():
|
||||
print(f" {status_label}: {count}")
|
||||
if total_units:
|
||||
unit_suffix = f" {unit_type}" if unit_type else ""
|
||||
print(f" Total Units: {total_units}{unit_suffix}")
|
||||
|
||||
elif args.command == "demo":
|
||||
print("🎭 Submitting demo inference job...")
|
||||
job_id = client.submit_job("inference", {
|
||||
"task": "text-generation",
|
||||
"model": "llama-2-7b",
|
||||
"prompt": "What is AITBC?",
|
||||
"parameters": {"max_tokens": 100}
|
||||
})
|
||||
|
||||
if job_id:
|
||||
print(f"✅ Demo job submitted!")
|
||||
print(f" Job ID: {job_id}")
|
||||
|
||||
# Check status after a moment
|
||||
import time
|
||||
time.sleep(2)
|
||||
status = client.get_job_status(job_id)
|
||||
if status:
|
||||
print(f"\n📊 Status: {status['state']}")
|
||||
print(f" Miner: {status.get('assigned_miner_id', 'unassigned')}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
217
cli/gpu_test.py
Executable file
217
cli/gpu_test.py
Executable file
@@ -0,0 +1,217 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
GPU Access Test - Check if miner can access local GPU resources
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import json
|
||||
import time
|
||||
import psutil
|
||||
|
||||
def check_nvidia_gpu():
|
||||
"""Check NVIDIA GPU availability"""
|
||||
print("🔍 Checking NVIDIA GPU...")
|
||||
|
||||
try:
|
||||
# Check nvidia-smi
|
||||
result = subprocess.run(
|
||||
["nvidia-smi", "--query-gpu=name,memory.total,memory.free,utilization.gpu",
|
||||
"--format=csv,noheader,nounits"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
lines = result.stdout.strip().split('\n')
|
||||
print(f"✅ NVIDIA GPU(s) Found: {len(lines)}")
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
parts = line.split(', ')
|
||||
if len(parts) >= 4:
|
||||
name = parts[0]
|
||||
total_mem = parts[1]
|
||||
free_mem = parts[2]
|
||||
util = parts[3]
|
||||
print(f"\n GPU {i}:")
|
||||
print(f" 📦 Model: {name}")
|
||||
print(f" 💾 Memory: {free_mem}/{total_mem} MB free")
|
||||
print(f" ⚡ Utilization: {util}%")
|
||||
|
||||
return True
|
||||
else:
|
||||
print("❌ nvidia-smi command failed")
|
||||
return False
|
||||
|
||||
except FileNotFoundError:
|
||||
print("❌ nvidia-smi not found - NVIDIA drivers not installed")
|
||||
return False
|
||||
|
||||
def check_cuda():
|
||||
"""Check CUDA availability"""
|
||||
print("\n🔍 Checking CUDA...")
|
||||
|
||||
try:
|
||||
# Try to import pynvml
|
||||
import pynvml
|
||||
pynvml.nvmlInit()
|
||||
|
||||
device_count = pynvml.nvmlDeviceGetCount()
|
||||
print(f"✅ CUDA Available - {device_count} device(s)")
|
||||
|
||||
for i in range(device_count):
|
||||
handle = pynvml.nvmlDeviceGetHandleByIndex(i)
|
||||
name = pynvml.nvmlDeviceGetName(handle).decode('utf-8')
|
||||
memory_info = pynvml.nvmlDeviceGetMemoryInfo(handle)
|
||||
|
||||
print(f"\n CUDA Device {i}:")
|
||||
print(f" 📦 Name: {name}")
|
||||
print(f" 💾 Memory: {memory_info.free // 1024**2}/{memory_info.total // 1024**2} MB free")
|
||||
|
||||
return True
|
||||
|
||||
except ImportError:
|
||||
print("⚠️ pynvml not installed - install with: pip install pynvml")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ CUDA error: {e}")
|
||||
return False
|
||||
|
||||
def check_pytorch():
|
||||
"""Check PyTorch CUDA support"""
|
||||
print("\n🔍 Checking PyTorch CUDA...")
|
||||
|
||||
try:
|
||||
import torch
|
||||
|
||||
print(f"✅ PyTorch Installed: {torch.__version__}")
|
||||
print(f" CUDA Available: {torch.cuda.is_available()}")
|
||||
|
||||
if torch.cuda.is_available():
|
||||
print(f" CUDA Version: {torch.version.cuda}")
|
||||
print(f" GPU Count: {torch.cuda.device_count()}")
|
||||
|
||||
for i in range(torch.cuda.device_count()):
|
||||
props = torch.cuda.get_device_properties(i)
|
||||
print(f"\n PyTorch GPU {i}:")
|
||||
print(f" 📦 Name: {props.name}")
|
||||
print(f" 💾 Memory: {props.total_memory // 1024**2} MB")
|
||||
print(f" Compute: {props.major}.{props.minor}")
|
||||
|
||||
return torch.cuda.is_available()
|
||||
|
||||
except ImportError:
|
||||
print("❌ PyTorch not installed - install with: pip install torch")
|
||||
return False
|
||||
|
||||
def run_gpu_stress_test(duration=10):
|
||||
"""Run a quick GPU stress test"""
|
||||
print(f"\n🔥 Running GPU Stress Test ({duration}s)...")
|
||||
|
||||
try:
|
||||
import torch
|
||||
|
||||
if not torch.cuda.is_available():
|
||||
print("❌ CUDA not available for stress test")
|
||||
return False
|
||||
|
||||
device = torch.device('cuda')
|
||||
|
||||
# Create tensors and perform matrix multiplication
|
||||
print(" ⚡ Performing matrix multiplications...")
|
||||
start_time = time.time()
|
||||
|
||||
while time.time() - start_time < duration:
|
||||
# Create large matrices
|
||||
a = torch.randn(1000, 1000, device=device)
|
||||
b = torch.randn(1000, 1000, device=device)
|
||||
|
||||
# Multiply them
|
||||
c = torch.mm(a, b)
|
||||
|
||||
# Sync to ensure computation completes
|
||||
torch.cuda.synchronize()
|
||||
|
||||
print("✅ Stress test completed successfully")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Stress test failed: {e}")
|
||||
return False
|
||||
|
||||
def check_system_resources():
|
||||
"""Check system resources"""
|
||||
print("\n💻 System Resources:")
|
||||
|
||||
# CPU
|
||||
cpu_percent = psutil.cpu_percent(interval=1)
|
||||
print(f" 🖥️ CPU Usage: {cpu_percent}%")
|
||||
print(f" 🧠 CPU Cores: {psutil.cpu_count()} logical, {psutil.cpu_count(logical=False)} physical")
|
||||
|
||||
# Memory
|
||||
memory = psutil.virtual_memory()
|
||||
print(f" 💾 RAM: {memory.used // 1024**2}/{memory.total // 1024**2} MB used ({memory.percent}%)")
|
||||
|
||||
# Disk
|
||||
disk = psutil.disk_usage('/')
|
||||
print(f" 💿 Disk: {disk.used // 1024**3}/{disk.total // 1024**3} GB used")
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="GPU Access Test for AITBC Miner")
|
||||
parser.add_argument("--stress", type=int, default=0, help="Run stress test for N seconds")
|
||||
parser.add_argument("--all", action="store_true", help="Run all tests including stress")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
print("🚀 AITBC GPU Access Test")
|
||||
print("=" * 60)
|
||||
|
||||
# Check system resources
|
||||
check_system_resources()
|
||||
|
||||
# Check GPU availability
|
||||
has_nvidia = check_nvidia_gpu()
|
||||
has_cuda = check_cuda()
|
||||
has_pytorch = check_pytorch()
|
||||
|
||||
# Summary
|
||||
print("\n📊 SUMMARY")
|
||||
print("=" * 60)
|
||||
|
||||
if has_nvidia or has_cuda or has_pytorch:
|
||||
print("✅ GPU is available for mining!")
|
||||
|
||||
if args.stress > 0 or args.all:
|
||||
run_gpu_stress_test(args.stress if args.stress > 0 else 10)
|
||||
|
||||
print("\n💡 Miner can run GPU-intensive tasks:")
|
||||
print(" • Model inference (LLaMA, Stable Diffusion)")
|
||||
print(" • Training jobs")
|
||||
print(" • Batch processing")
|
||||
|
||||
else:
|
||||
print("❌ No GPU available - miner will run in CPU-only mode")
|
||||
print("\n💡 To enable GPU mining:")
|
||||
print(" 1. Install NVIDIA drivers")
|
||||
print(" 2. Install CUDA toolkit")
|
||||
print(" 3. Install PyTorch with CUDA: pip install torch")
|
||||
|
||||
# Check if miner service is running
|
||||
print("\n🔍 Checking miner service...")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["systemctl", "is-active", "aitbc-gpu-miner"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
if result.stdout.strip() == "active":
|
||||
print("✅ Miner service is running")
|
||||
else:
|
||||
print("⚠️ Miner service is not running")
|
||||
print(" Start with: sudo systemctl start aitbc-gpu-miner")
|
||||
except:
|
||||
print("⚠️ Could not check miner service status")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
259
cli/miner.py
Executable file
259
cli/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:8001"
|
||||
DEFAULT_API_KEY = "REDACTED_MINER_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()
|
||||
286
cli/miner_gpu_test.py
Executable file
286
cli/miner_gpu_test.py
Executable file
@@ -0,0 +1,286 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Miner GPU Test - Test if the miner service can access and utilize GPU
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import httpx
|
||||
import json
|
||||
import time
|
||||
import sys
|
||||
|
||||
# Configuration
|
||||
DEFAULT_COORDINATOR = "http://localhost:8001"
|
||||
DEFAULT_API_KEY = "REDACTED_MINER_KEY"
|
||||
DEFAULT_MINER_ID = "localhost-gpu-miner"
|
||||
|
||||
def test_miner_registration(coordinator_url):
|
||||
"""Test if miner can register with GPU capabilities"""
|
||||
print("📝 Testing Miner Registration...")
|
||||
|
||||
gpu_capabilities = {
|
||||
"gpu": {
|
||||
"model": "NVIDIA GeForce RTX 4060 Ti",
|
||||
"memory_gb": 16,
|
||||
"cuda_version": "12.1",
|
||||
"compute_capability": "8.9"
|
||||
},
|
||||
"compute": {
|
||||
"type": "GPU",
|
||||
"platform": "CUDA",
|
||||
"supported_tasks": ["inference", "training", "stable-diffusion", "llama"],
|
||||
"max_concurrent_jobs": 1
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{coordinator_url}/v1/miners/register?miner_id={DEFAULT_MINER_ID}",
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"X-Api-Key": DEFAULT_API_KEY
|
||||
},
|
||||
json={"capabilities": gpu_capabilities}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
print("✅ Miner registered with GPU capabilities")
|
||||
print(f" GPU Model: {gpu_capabilities['gpu']['model']}")
|
||||
print(f" Memory: {gpu_capabilities['gpu']['memory_gb']} GB")
|
||||
print(f" CUDA: {gpu_capabilities['gpu']['cuda_version']}")
|
||||
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 test_job_processing(coordinator_url):
|
||||
"""Test if miner can process a GPU job"""
|
||||
print("\n⚙️ Testing GPU Job Processing...")
|
||||
|
||||
# First submit a test job
|
||||
print(" 1. Submitting test job...")
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
# Submit job as client
|
||||
job_response = client.post(
|
||||
f"{coordinator_url}/v1/jobs",
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"X-Api-Key": "REDACTED_CLIENT_KEY"
|
||||
},
|
||||
json={
|
||||
"payload": {
|
||||
"type": "inference",
|
||||
"task": "gpu-test",
|
||||
"model": "test-gpu-model",
|
||||
"parameters": {
|
||||
"require_gpu": True,
|
||||
"memory_gb": 8
|
||||
}
|
||||
},
|
||||
"ttl_seconds": 300
|
||||
}
|
||||
)
|
||||
|
||||
if job_response.status_code != 201:
|
||||
print(f"❌ Failed to submit job: {job_response.status_code}")
|
||||
return False
|
||||
|
||||
job_id = job_response.json()['job_id']
|
||||
print(f" ✅ Job submitted: {job_id}")
|
||||
|
||||
# Poll for the job as miner
|
||||
print(" 2. Polling for job...")
|
||||
poll_response = client.post(
|
||||
f"{coordinator_url}/v1/miners/poll",
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"X-Api-Key": DEFAULT_API_KEY
|
||||
},
|
||||
json={"max_wait_seconds": 5}
|
||||
)
|
||||
|
||||
if poll_response.status_code == 200:
|
||||
job = poll_response.json()
|
||||
print(f" ✅ Job received: {job['job_id']}")
|
||||
|
||||
# Simulate GPU processing
|
||||
print(" 3. Simulating GPU processing...")
|
||||
time.sleep(2)
|
||||
|
||||
# Submit result
|
||||
print(" 4. Submitting result...")
|
||||
result_response = client.post(
|
||||
f"{coordinator_url}/v1/miners/{job['job_id']}/result",
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"X-Api-Key": DEFAULT_API_KEY
|
||||
},
|
||||
json={
|
||||
"result": {
|
||||
"status": "completed",
|
||||
"output": "GPU task completed successfully",
|
||||
"execution_time_ms": 2000,
|
||||
"gpu_utilization": 85,
|
||||
"memory_used_mb": 4096
|
||||
},
|
||||
"metrics": {
|
||||
"compute_time": 2.0,
|
||||
"energy_used": 0.05,
|
||||
"aitbc_earned": 25.0
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if result_response.status_code == 200:
|
||||
print(" ✅ Result submitted successfully")
|
||||
print(f" 💰 Earned: 25.0 AITBC")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ Failed to submit result: {result_response.status_code}")
|
||||
return False
|
||||
|
||||
elif poll_response.status_code == 204:
|
||||
print(" ⚠️ No jobs available")
|
||||
return False
|
||||
else:
|
||||
print(f"❌ Poll failed: {poll_response.status_code}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return False
|
||||
|
||||
def test_gpu_heartbeat(coordinator_url):
|
||||
"""Test sending GPU metrics in heartbeat"""
|
||||
print("\n💓 Testing GPU Heartbeat...")
|
||||
|
||||
heartbeat_data = {
|
||||
"status": "ONLINE",
|
||||
"inflight": 0,
|
||||
"metadata": {
|
||||
"last_seen": time.time(),
|
||||
"gpu_utilization": 45,
|
||||
"gpu_memory_used": 8192,
|
||||
"gpu_temperature": 68,
|
||||
"gpu_power_usage": 220,
|
||||
"cuda_version": "12.1",
|
||||
"driver_version": "535.104.05"
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{coordinator_url}/v1/miners/heartbeat?miner_id={DEFAULT_MINER_ID}",
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"X-Api-Key": DEFAULT_API_KEY
|
||||
},
|
||||
json=heartbeat_data
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
print("✅ GPU heartbeat sent successfully")
|
||||
print(f" GPU Utilization: {heartbeat_data['metadata']['gpu_utilization']}%")
|
||||
print(f" Memory Used: {heartbeat_data['metadata']['gpu_memory_used']} MB")
|
||||
print(f" Temperature: {heartbeat_data['metadata']['gpu_temperature']}°C")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ Heartbeat failed: {response.status_code}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return False
|
||||
|
||||
def check_blockchain_status(coordinator_url):
|
||||
"""Check if processed jobs appear in blockchain"""
|
||||
print("\n📦 Checking Blockchain Status...")
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(f"{coordinator_url}/v1/explorer/blocks")
|
||||
|
||||
if response.status_code == 200:
|
||||
blocks = response.json()
|
||||
print(f"✅ Found {len(blocks['items'])} blocks")
|
||||
|
||||
# Show latest blocks
|
||||
for i, block in enumerate(blocks['items'][:3]):
|
||||
print(f"\n Block {block['height']}:")
|
||||
print(f" Hash: {block['hash']}")
|
||||
print(f" Proposer: {block['proposer']}")
|
||||
print(f" Time: {block['timestamp']}")
|
||||
|
||||
return True
|
||||
else:
|
||||
print(f"❌ Failed to get blocks: {response.status_code}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Test Miner GPU Access")
|
||||
parser.add_argument("--url", help="Coordinator URL")
|
||||
parser.add_argument("--full", action="store_true", help="Run full test suite")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
coordinator_url = args.url if args.url else DEFAULT_COORDINATOR
|
||||
|
||||
print("🚀 AITBC Miner GPU Test")
|
||||
print("=" * 60)
|
||||
print(f"Coordinator: {coordinator_url}")
|
||||
print(f"Miner ID: {DEFAULT_MINER_ID}")
|
||||
print()
|
||||
|
||||
# Run tests
|
||||
tests = [
|
||||
("Miner Registration", lambda: test_miner_registration(coordinator_url)),
|
||||
("GPU Heartbeat", lambda: test_gpu_heartbeat(coordinator_url)),
|
||||
]
|
||||
|
||||
if args.full:
|
||||
tests.append(("Job Processing", lambda: test_job_processing(coordinator_url)))
|
||||
tests.append(("Blockchain Status", lambda: check_blockchain_status(coordinator_url)))
|
||||
|
||||
results = []
|
||||
|
||||
for test_name, test_func in tests:
|
||||
print(f"🧪 Running: {test_name}")
|
||||
result = test_func()
|
||||
results.append((test_name, result))
|
||||
print()
|
||||
|
||||
# Summary
|
||||
print("📊 TEST RESULTS")
|
||||
print("=" * 60)
|
||||
|
||||
passed = 0
|
||||
for test_name, result in results:
|
||||
status = "✅ PASS" if result else "❌ FAIL"
|
||||
print(f"{status} {test_name}")
|
||||
if result:
|
||||
passed += 1
|
||||
|
||||
print(f"\nScore: {passed}/{len(results)} tests passed")
|
||||
|
||||
if passed == len(results):
|
||||
print("\n🎉 All tests passed! Miner is ready for GPU mining.")
|
||||
print("\n💡 Next steps:")
|
||||
print(" 1. Start continuous mining: python3 cli/miner.py mine")
|
||||
print(" 2. Monitor earnings: cd home/miner && python3 wallet.py balance")
|
||||
else:
|
||||
print("\n⚠️ Some tests failed. Check the errors above.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
84
cli/test_gpu_access.py
Executable file
84
cli/test_gpu_access.py
Executable file
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple GPU Access Test - Verify miner can access GPU
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
def main():
|
||||
print("🔍 GPU Access Test for AITBC Miner")
|
||||
print("=" * 50)
|
||||
|
||||
# Check if nvidia-smi is available
|
||||
print("\n1. Checking NVIDIA GPU...")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["nvidia-smi", "--query-gpu=name,memory.total", "--format=csv,noheader"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
gpu_info = result.stdout.strip()
|
||||
print(f"✅ GPU Found: {gpu_info}")
|
||||
else:
|
||||
print("❌ No NVIDIA GPU detected")
|
||||
sys.exit(1)
|
||||
except FileNotFoundError:
|
||||
print("❌ nvidia-smi not found")
|
||||
sys.exit(1)
|
||||
|
||||
# Check CUDA with PyTorch
|
||||
print("\n2. Checking CUDA with PyTorch...")
|
||||
try:
|
||||
import torch
|
||||
|
||||
if torch.cuda.is_available():
|
||||
print(f"✅ CUDA Available: {torch.version.cuda}")
|
||||
print(f" GPU Count: {torch.cuda.device_count()}")
|
||||
|
||||
device = torch.device('cuda')
|
||||
|
||||
# Test computation
|
||||
print("\n3. Testing GPU computation...")
|
||||
a = torch.randn(1000, 1000, device=device)
|
||||
b = torch.randn(1000, 1000, device=device)
|
||||
c = torch.mm(a, b)
|
||||
|
||||
print("✅ GPU computation successful")
|
||||
|
||||
# Check memory
|
||||
memory_allocated = torch.cuda.memory_allocated() / 1024**2
|
||||
print(f" Memory used: {memory_allocated:.2f} MB")
|
||||
|
||||
else:
|
||||
print("❌ CUDA not available in PyTorch")
|
||||
sys.exit(1)
|
||||
|
||||
except ImportError:
|
||||
print("❌ PyTorch not installed")
|
||||
sys.exit(1)
|
||||
|
||||
# Check miner service
|
||||
print("\n4. Checking miner service...")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["systemctl", "is-active", "aitbc-gpu-miner"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
if result.stdout.strip() == "active":
|
||||
print("✅ Miner service is running")
|
||||
else:
|
||||
print("⚠️ Miner service is not running")
|
||||
except:
|
||||
print("⚠️ Could not check miner service")
|
||||
|
||||
print("\n✅ GPU access test completed!")
|
||||
print("\n💡 Your GPU is ready for mining AITBC!")
|
||||
print(" Start mining with: python3 cli/miner.py mine")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
128
cli/test_ollama_gpu_provider.py
Normal file
128
cli/test_ollama_gpu_provider.py
Normal file
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Ollama GPU Provider Test
|
||||
Submits an inference job with prompt "hello" and verifies completion.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
|
||||
DEFAULT_COORDINATOR = "http://127.0.0.1:18000"
|
||||
DEFAULT_API_KEY = "REDACTED_CLIENT_KEY"
|
||||
DEFAULT_PROMPT = "hello"
|
||||
DEFAULT_TIMEOUT = 180
|
||||
POLL_INTERVAL = 3
|
||||
|
||||
|
||||
def submit_job(client: httpx.Client, base_url: str, api_key: str, prompt: str) -> Optional[str]:
|
||||
payload = {
|
||||
"payload": {
|
||||
"type": "inference",
|
||||
"prompt": prompt,
|
||||
"parameters": {"prompt": prompt},
|
||||
},
|
||||
"ttl_seconds": 900,
|
||||
}
|
||||
response = client.post(
|
||||
f"{base_url}/v1/jobs",
|
||||
headers={"X-Api-Key": api_key, "Content-Type": "application/json"},
|
||||
json=payload,
|
||||
timeout=10,
|
||||
)
|
||||
if response.status_code != 201:
|
||||
print(f"❌ Job submission failed: {response.status_code} {response.text}")
|
||||
return None
|
||||
return response_seen_id(response)
|
||||
|
||||
|
||||
def response_seen_id(response: httpx.Response) -> Optional[str]:
|
||||
try:
|
||||
return response.json().get("job_id")
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def fetch_status(client: httpx.Client, base_url: str, api_key: str, job_id: str) -> Optional[dict]:
|
||||
response = client.get(
|
||||
f"{base_url}/v1/jobs/{job_id}",
|
||||
headers={"X-Api-Key": api_key},
|
||||
timeout=10,
|
||||
)
|
||||
if response.status_code != 200:
|
||||
print(f"❌ Status check failed: {response.status_code} {response.text}")
|
||||
return None
|
||||
return response.json()
|
||||
|
||||
|
||||
def fetch_result(client: httpx.Client, base_url: str, api_key: str, job_id: str) -> Optional[dict]:
|
||||
response = client.get(
|
||||
f"{base_url}/v1/jobs/{job_id}/result",
|
||||
headers={"X-Api-Key": api_key},
|
||||
timeout=10,
|
||||
)
|
||||
if response.status_code != 200:
|
||||
print(f"❌ Result fetch failed: {response.status_code} {response.text}")
|
||||
return None
|
||||
return response.json()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Ollama GPU provider end-to-end test")
|
||||
parser.add_argument("--url", default=DEFAULT_COORDINATOR, help="Coordinator base URL")
|
||||
parser.add_argument("--api-key", default=DEFAULT_API_KEY, help="Client API key")
|
||||
parser.add_argument("--prompt", default=DEFAULT_PROMPT, help="Prompt to send")
|
||||
parser.add_argument("--timeout", type=int, default=DEFAULT_TIMEOUT, help="Timeout in seconds")
|
||||
args = parser.parse_args()
|
||||
|
||||
with httpx.Client() as client:
|
||||
print("🧪 Submitting GPU provider job...")
|
||||
job_id = submit_job(client, args.url, args.api_key, args.prompt)
|
||||
if not job_id:
|
||||
return 1
|
||||
print(f"✅ Job submitted: {job_id}")
|
||||
|
||||
deadline = time.time() + args.timeout
|
||||
status = None
|
||||
while time.time() < deadline:
|
||||
status = fetch_status(client, args.url, args.api_key, job_id)
|
||||
if not status:
|
||||
return 1
|
||||
state = status.get("state")
|
||||
print(f"⏳ Job state: {state}")
|
||||
if state == "COMPLETED":
|
||||
break
|
||||
if state in {"FAILED", "CANCELED", "EXPIRED"}:
|
||||
print(f"❌ Job ended in state: {state}")
|
||||
return 1
|
||||
time.sleep(POLL_INTERVAL)
|
||||
|
||||
if not status or status.get("state") != "COMPLETED":
|
||||
print("❌ Job did not complete within timeout")
|
||||
return 1
|
||||
|
||||
result = fetch_result(client, args.url, args.api_key, job_id)
|
||||
if result is None:
|
||||
return 1
|
||||
|
||||
payload = result.get("result") or {}
|
||||
output = payload.get("output")
|
||||
receipt = result.get("receipt")
|
||||
if not output:
|
||||
print("❌ Missing output in job result")
|
||||
return 1
|
||||
if not receipt:
|
||||
print("❌ Missing receipt in job result (payment/settlement not recorded)")
|
||||
return 1
|
||||
|
||||
print("✅ GPU provider job completed")
|
||||
print(f"📝 Output: {output}")
|
||||
print(f"🧾 Receipt ID: {receipt.get('receipt_id')}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
109
cli/test_workflow.py
Executable file
109
cli/test_workflow.py
Executable file
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Complete AITBC workflow test - Client submits job, miner processes it, earns AITBC
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
|
||||
def run_command(cmd, description):
|
||||
"""Run a CLI command and display results"""
|
||||
print(f"\n{'='*60}")
|
||||
print(f"🔧 {description}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
||||
print(result.stdout)
|
||||
|
||||
if result.stderr:
|
||||
print(f"Errors: {result.stderr}")
|
||||
|
||||
return result.returncode == 0
|
||||
|
||||
def main():
|
||||
print("🚀 AITBC Complete Workflow Test")
|
||||
print("=" * 60)
|
||||
|
||||
# Get the directory of this script
|
||||
cli_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
# 1. Check current blocks
|
||||
run_command(
|
||||
f"python3 {cli_dir}/client.py blocks --limit 3",
|
||||
"Checking current blocks"
|
||||
)
|
||||
|
||||
# 2. Register miner
|
||||
run_command(
|
||||
f"python3 {cli_dir}/miner.py register --gpu RTX 4090 --memory 24",
|
||||
"Registering miner"
|
||||
)
|
||||
|
||||
# 3. Submit a job from client
|
||||
run_command(
|
||||
f"python3 {cli_dir}/client.py submit inference --model llama-2-7b --prompt 'What is blockchain?'",
|
||||
"Client submitting inference job"
|
||||
)
|
||||
|
||||
# 4. Miner polls for and processes the job
|
||||
print(f"\n{'='*60}")
|
||||
print("⛏️ Miner polling for job (will wait up to 10 seconds)...")
|
||||
print(f"{'='*60}")
|
||||
|
||||
# Run miner in poll mode repeatedly
|
||||
for i in range(5):
|
||||
result = subprocess.run(
|
||||
f"python3 {cli_dir}/miner.py poll --wait 2",
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
print(result.stdout)
|
||||
|
||||
if "job_id" in result.stdout:
|
||||
print("✅ Job found! Processing...")
|
||||
time.sleep(2)
|
||||
break
|
||||
|
||||
if i < 4:
|
||||
print("💤 No job yet, trying again...")
|
||||
time.sleep(2)
|
||||
|
||||
# 5. Check updated blocks
|
||||
run_command(
|
||||
f"python3 {cli_dir}/client.py blocks --limit 3",
|
||||
"Checking updated blocks (should show proposer)"
|
||||
)
|
||||
|
||||
# 6. Check wallet
|
||||
run_command(
|
||||
f"python3 {cli_dir}/wallet.py balance",
|
||||
"Checking wallet balance"
|
||||
)
|
||||
|
||||
# Add earnings manually (in real system, this would be automatic)
|
||||
run_command(
|
||||
f"python3 {cli_dir}/wallet.py earn 10.0 --job demo-job-123 --desc 'Inference task completed'",
|
||||
"Adding earnings to wallet"
|
||||
)
|
||||
|
||||
# 7. Final wallet status
|
||||
run_command(
|
||||
f"python3 {cli_dir}/wallet.py history",
|
||||
"Showing transaction history"
|
||||
)
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print("✅ Workflow test complete!")
|
||||
print("💡 Tips:")
|
||||
print(" - Use 'python3 cli/client.py --help' for client commands")
|
||||
print(" - Use 'python3 cli/miner.py --help' for miner commands")
|
||||
print(" - Use 'python3 cli/wallet.py --help' for wallet commands")
|
||||
print(" - Run 'python3 cli/miner.py mine' for continuous mining")
|
||||
print(f"{'='*60}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
158
cli/wallet.py
Executable file
158
cli/wallet.py
Executable file
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
AITBC Wallet CLI Tool - Track earnings and manage wallet
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import Dict, List
|
||||
|
||||
class AITBCWallet:
|
||||
def __init__(self, wallet_file: str = None):
|
||||
if wallet_file is None:
|
||||
wallet_file = os.path.expanduser("~/.aitbc_wallet.json")
|
||||
|
||||
self.wallet_file = wallet_file
|
||||
self.data = self._load_wallet()
|
||||
|
||||
def _load_wallet(self) -> dict:
|
||||
"""Load wallet data from file"""
|
||||
if os.path.exists(self.wallet_file):
|
||||
try:
|
||||
with open(self.wallet_file, 'r') as f:
|
||||
return json.load(f)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Create new wallet
|
||||
return {
|
||||
"address": "aitbc1" + os.urandom(10).hex(),
|
||||
"balance": 0.0,
|
||||
"transactions": [],
|
||||
"created_at": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
def save(self):
|
||||
"""Save wallet to file"""
|
||||
with open(self.wallet_file, 'w') as f:
|
||||
json.dump(self.data, f, indent=2)
|
||||
|
||||
def add_earnings(self, amount: float, job_id: str, description: str = ""):
|
||||
"""Add earnings from completed job"""
|
||||
transaction = {
|
||||
"type": "earn",
|
||||
"amount": amount,
|
||||
"job_id": job_id,
|
||||
"description": description or f"Job {job_id}",
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
self.data["transactions"].append(transaction)
|
||||
self.data["balance"] += amount
|
||||
self.save()
|
||||
|
||||
print(f"💰 Added {amount} AITBC to wallet")
|
||||
print(f" New balance: {self.data['balance']} AITBC")
|
||||
|
||||
def spend(self, amount: float, description: str):
|
||||
"""Spend AITBC"""
|
||||
if self.data["balance"] < amount:
|
||||
print(f"❌ Insufficient balance!")
|
||||
print(f" Balance: {self.data['balance']} AITBC")
|
||||
print(f" Needed: {amount} AITBC")
|
||||
return False
|
||||
|
||||
transaction = {
|
||||
"type": "spend",
|
||||
"amount": -amount,
|
||||
"description": description,
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
self.data["transactions"].append(transaction)
|
||||
self.data["balance"] -= amount
|
||||
self.save()
|
||||
|
||||
print(f"💸 Spent {amount} AITBC")
|
||||
print(f" Remaining: {self.data['balance']} AITBC")
|
||||
return True
|
||||
|
||||
def show_balance(self):
|
||||
"""Show wallet balance"""
|
||||
print(f"💳 Wallet Address: {self.data['address']}")
|
||||
print(f"💰 Balance: {self.data['balance']} AITBC")
|
||||
print(f"📊 Total Transactions: {len(self.data['transactions'])}")
|
||||
|
||||
def show_history(self, limit: int = 10):
|
||||
"""Show transaction history"""
|
||||
transactions = self.data["transactions"][-limit:]
|
||||
|
||||
if not transactions:
|
||||
print("📭 No transactions yet")
|
||||
return
|
||||
|
||||
print(f"📜 Recent Transactions (last {limit}):")
|
||||
print("-" * 60)
|
||||
|
||||
for tx in reversed(transactions):
|
||||
symbol = "💰" if tx["type"] == "earn" else "💸"
|
||||
print(f"{symbol} {tx['amount']:+8.2f} AITBC | {tx.get('description', 'N/A')}")
|
||||
print(f" 📅 {tx['timestamp']}")
|
||||
if "job_id" in tx:
|
||||
print(f" 🆔 Job: {tx['job_id']}")
|
||||
print()
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="AITBC Wallet CLI")
|
||||
parser.add_argument("--wallet", help="Wallet file path")
|
||||
|
||||
subparsers = parser.add_subparsers(dest="command", help="Commands")
|
||||
|
||||
# Balance command
|
||||
balance_parser = subparsers.add_parser("balance", help="Show balance")
|
||||
|
||||
# History command
|
||||
history_parser = subparsers.add_parser("history", help="Show transaction history")
|
||||
history_parser.add_argument("--limit", type=int, default=10, help="Number of transactions")
|
||||
|
||||
# Earn command
|
||||
earn_parser = subparsers.add_parser("earn", help="Add earnings")
|
||||
earn_parser.add_argument("amount", type=float, help="Amount earned")
|
||||
earn_parser.add_argument("--job", help="Job ID")
|
||||
earn_parser.add_argument("--desc", help="Description")
|
||||
|
||||
# Spend command
|
||||
spend_parser = subparsers.add_parser("spend", help="Spend AITBC")
|
||||
spend_parser.add_argument("amount", type=float, help="Amount to spend")
|
||||
spend_parser.add_argument("description", help="What you're spending on")
|
||||
|
||||
# Address command
|
||||
address_parser = subparsers.add_parser("address", help="Show wallet address")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.command:
|
||||
parser.print_help()
|
||||
return
|
||||
|
||||
wallet = AITBCWallet(args.wallet)
|
||||
|
||||
if args.command == "balance":
|
||||
wallet.show_balance()
|
||||
|
||||
elif args.command == "history":
|
||||
wallet.show_history(args.limit)
|
||||
|
||||
elif args.command == "earn":
|
||||
wallet.add_earnings(args.amount, args.job or "unknown", args.desc or "")
|
||||
|
||||
elif args.command == "spend":
|
||||
wallet.spend(args.amount, args.description)
|
||||
|
||||
elif args.command == "address":
|
||||
print(f"💳 Wallet Address: {wallet.data['address']}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user