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:
oib
2026-01-24 14:44:51 +01:00
parent 99bf335970
commit 9b9c5beb23
214 changed files with 25558 additions and 171 deletions

155
cli/README.md Normal file
View 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
View 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
View 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
View 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
View 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
View 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()

View 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
View 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
View 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()