fix: add fixed marketplace offers endpoint to avoid AttributeError
Some checks failed
API Endpoint Tests / test-api-endpoints (push) Successful in 37s
Integration Tests / test-service-integration (push) Successful in 57s
Python Tests / test-python (push) Failing after 4m15s
CLI Tests / test-cli (push) Failing after 6m48s
Security Scanning / security-scan (push) Successful in 2m16s
Some checks failed
API Endpoint Tests / test-api-endpoints (push) Successful in 37s
Integration Tests / test-service-integration (push) Successful in 57s
Python Tests / test-python (push) Failing after 4m15s
CLI Tests / test-cli (push) Failing after 6m48s
Security Scanning / security-scan (push) Successful in 2m16s
Marketplace Offers Router Enhancement: ✅ NEW ENDPOINT: GET /offers for listing all marketplace offers - Added fixed version to avoid AttributeError from GlobalMarketplaceService - Uses direct database query with SQLModel select - Safely extracts offer attributes with fallback defaults - Returns structured offer data with GPU specs and metadata ✅ ENDPOINT FEATURES: 🔧 Direct Query: Bypasses service layer to avoid attribute
This commit is contained in:
1
aitbc-miner
Symbolic link
1
aitbc-miner
Symbolic link
@@ -0,0 +1 @@
|
||||
/opt/aitbc/cli/miner_cli.py
|
||||
@@ -7,12 +7,15 @@ Router to create marketplace offers from registered miners
|
||||
from typing import Any
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlmodel import Session, select
|
||||
import logging
|
||||
|
||||
from ..deps import require_admin_key
|
||||
from ..domain import MarketplaceOffer, Miner
|
||||
from ..schemas import MarketplaceOfferView
|
||||
from ..storage import get_session
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(tags=["marketplace-offers"])
|
||||
|
||||
|
||||
@@ -102,3 +105,39 @@ async def list_miner_offers(session: Annotated[Session, Depends(get_session)]) -
|
||||
result.append(offer_view)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/offers", summary="List all marketplace offers (Fixed)")
|
||||
async def list_all_offers(session: Annotated[Session, Depends(get_session)]) -> list[dict[str, Any]]:
|
||||
"""List all marketplace offers - Fixed version to avoid AttributeError"""
|
||||
try:
|
||||
# Use direct database query instead of GlobalMarketplaceService
|
||||
from sqlmodel import select
|
||||
|
||||
offers = session.execute(select(MarketplaceOffer)).scalars().all()
|
||||
|
||||
result = []
|
||||
for offer in offers:
|
||||
# Extract attributes safely
|
||||
attrs = offer.attributes or {}
|
||||
|
||||
offer_data = {
|
||||
"id": offer.id,
|
||||
"provider": offer.provider,
|
||||
"capacity": offer.capacity,
|
||||
"price": offer.price,
|
||||
"status": offer.status,
|
||||
"created_at": offer.created_at.isoformat(),
|
||||
"gpu_model": attrs.get("gpu_model", "Unknown"),
|
||||
"gpu_memory_gb": attrs.get("gpu_memory_gb", 0),
|
||||
"cuda_version": attrs.get("cuda_version", "Unknown"),
|
||||
"supported_models": attrs.get("supported_models", []),
|
||||
"region": attrs.get("region", "unknown")
|
||||
}
|
||||
result.append(offer_data)
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error listing offers: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
50
cli/integrate_miner_cli.sh
Executable file
50
cli/integrate_miner_cli.sh
Executable file
@@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
# AITBC Miner Management Integration Script
|
||||
# This script integrates the miner management functionality with the main AITBC CLI
|
||||
|
||||
echo "🤖 AITBC Miner Management Integration"
|
||||
echo "=================================="
|
||||
|
||||
# Check if miner CLI exists
|
||||
MINER_CLI="/opt/aitbc/cli/miner_cli.py"
|
||||
if [ ! -f "$MINER_CLI" ]; then
|
||||
echo "❌ Error: Miner CLI not found at $MINER_CLI"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create a symlink in the main CLI directory
|
||||
MAIN_CLI_DIR="/opt/aitbc"
|
||||
MINER_CMD="$MAIN_CLI_DIR/aitbc-miner"
|
||||
|
||||
if [ ! -L "$MINER_CMD" ]; then
|
||||
echo "🔗 Creating symlink: $MINER_CMD -> $MINER_CLI"
|
||||
ln -s "$MINER_CLI" "$MINER_CMD"
|
||||
chmod +x "$MINER_CMD"
|
||||
fi
|
||||
|
||||
# Test the integration
|
||||
echo "🧪 Testing miner CLI integration..."
|
||||
echo ""
|
||||
|
||||
# Test help
|
||||
echo "📋 Testing help command:"
|
||||
$MINER_CMD --help | head -10
|
||||
echo ""
|
||||
|
||||
# Test registration (with test data)
|
||||
echo "📝 Testing registration command:"
|
||||
$MINER_CMD register --miner-id integration-test --wallet ait113e1941cb60f3bb945ec9d412527b6048b73eb2d --gpu-memory 2048 --models qwen3:8b --pricing 0.45 --region integration-test 2>/dev/null | grep "Status:"
|
||||
echo ""
|
||||
|
||||
echo "✅ Miner CLI integration completed!"
|
||||
echo ""
|
||||
echo "🚀 Usage Examples:"
|
||||
echo " $MINER_CMD register --miner-id my-miner --wallet <wallet> --gpu-memory 8192 --models qwen3:8b --pricing 0.50"
|
||||
echo " $MINER_CMD status --miner-id my-miner"
|
||||
echo " $MINER_CMD poll --miner-id my-miner"
|
||||
echo " $MINER_CMD heartbeat --miner-id my-miner"
|
||||
echo " $MINER_CMD result --job-id <job-id> --miner-id my-miner --result 'Job completed'"
|
||||
echo " $MINER_CMD marketplace list"
|
||||
echo " $MINER_CMD marketplace create --miner-id my-miner --price 0.75"
|
||||
echo ""
|
||||
echo "📚 All miner management commands are now available via: $MINER_CMD"
|
||||
254
cli/miner_cli.py
Executable file
254
cli/miner_cli.py
Executable file
@@ -0,0 +1,254 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
AITBC Miner CLI Extension
|
||||
Adds comprehensive miner management commands to AITBC CLI
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
# Add the CLI directory to path
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
try:
|
||||
from miner_management import miner_cli_dispatcher
|
||||
except ImportError:
|
||||
print("❌ Error: miner_management module not found")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main CLI entry point for miner management"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="AITBC AI Compute Miner Management",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
# Register as AI compute provider
|
||||
python miner_cli.py register --miner-id ai-miner-1 --wallet ait1xyz --gpu-memory 8192 --models qwen3:8b llama3:8b --pricing 0.50
|
||||
|
||||
# Check miner status
|
||||
python miner_cli.py status --miner-id ai-miner-1
|
||||
|
||||
# Poll for jobs
|
||||
python miner_cli.py poll --miner-id ai-miner-1 --max-wait 60
|
||||
|
||||
# Submit job result
|
||||
python miner_cli.py result --job-id job123 --miner-id ai-miner-1 --result "Job completed successfully" --success
|
||||
|
||||
# List marketplace offers
|
||||
python miner_cli.py marketplace list --region us-west
|
||||
|
||||
# Create marketplace offer
|
||||
python miner_cli.py marketplace create --miner-id ai-miner-1 --price 0.75 --capacity 2
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument("--coordinator-url", default="http://localhost:8000",
|
||||
help="Coordinator API URL")
|
||||
parser.add_argument("--api-key", default="miner_prod_key_use_real_value",
|
||||
help="Miner API key")
|
||||
|
||||
subparsers = parser.add_subparsers(dest="action", help="Miner management actions")
|
||||
|
||||
# Register command
|
||||
register_parser = subparsers.add_parser("register", help="Register as AI compute provider")
|
||||
register_parser.add_argument("--miner-id", required=True, help="Unique miner identifier")
|
||||
register_parser.add_argument("--wallet", required=True, help="Wallet address for rewards")
|
||||
register_parser.add_argument("--capabilities", help="JSON string of miner capabilities")
|
||||
register_parser.add_argument("--gpu-memory", type=int, help="GPU memory in MB")
|
||||
register_parser.add_argument("--models", nargs="+", help="Supported AI models")
|
||||
register_parser.add_argument("--pricing", type=float, help="Price per hour")
|
||||
register_parser.add_argument("--concurrency", type=int, default=1, help="Max concurrent jobs")
|
||||
register_parser.add_argument("--region", help="Geographic region")
|
||||
|
||||
# Status command
|
||||
status_parser = subparsers.add_parser("status", help="Get miner status")
|
||||
status_parser.add_argument("--miner-id", required=True, help="Miner identifier")
|
||||
|
||||
# Heartbeat command
|
||||
heartbeat_parser = subparsers.add_parser("heartbeat", help="Send miner heartbeat")
|
||||
heartbeat_parser.add_argument("--miner-id", required=True, help="Miner identifier")
|
||||
heartbeat_parser.add_argument("--inflight", type=int, default=0, help="Currently running jobs")
|
||||
heartbeat_parser.add_argument("--status", default="ONLINE", help="Miner status")
|
||||
|
||||
# Poll command
|
||||
poll_parser = subparsers.add_parser("poll", help="Poll for available jobs")
|
||||
poll_parser.add_argument("--miner-id", required=True, help="Miner identifier")
|
||||
poll_parser.add_argument("--max-wait", type=int, default=30, help="Max wait time in seconds")
|
||||
poll_parser.add_argument("--auto-execute", action="store_true", help="Automatically execute assigned jobs")
|
||||
|
||||
# Result command
|
||||
result_parser = subparsers.add_parser("result", help="Submit job result")
|
||||
result_parser.add_argument("--job-id", required=True, help="Job identifier")
|
||||
result_parser.add_argument("--miner-id", required=True, help="Miner identifier")
|
||||
result_parser.add_argument("--result", help="Job result (JSON string)")
|
||||
result_parser.add_argument("--result-file", help="File containing job result")
|
||||
result_parser.add_argument("--success", action="store_true", help="Job completed successfully")
|
||||
result_parser.add_argument("--duration", type=int, help="Job duration in milliseconds")
|
||||
|
||||
# Update command
|
||||
update_parser = subparsers.add_parser("update", help="Update miner capabilities")
|
||||
update_parser.add_argument("--miner-id", required=True, help="Miner identifier")
|
||||
update_parser.add_argument("--capabilities", help="JSON string of updated capabilities")
|
||||
update_parser.add_argument("--gpu-memory", type=int, help="Updated GPU memory in MB")
|
||||
update_parser.add_argument("--models", nargs="+", help="Updated supported AI models")
|
||||
update_parser.add_argument("--pricing", type=float, help="Updated price per hour")
|
||||
update_parser.add_argument("--concurrency", type=int, help="Updated max concurrent jobs")
|
||||
update_parser.add_argument("--region", help="Updated geographic region")
|
||||
update_parser.add_argument("--wallet", help="Updated wallet address")
|
||||
|
||||
# Earnings command
|
||||
earnings_parser = subparsers.add_parser("earnings", help="Check miner earnings")
|
||||
earnings_parser.add_argument("--miner-id", required=True, help="Miner identifier")
|
||||
earnings_parser.add_argument("--period", choices=["day", "week", "month", "all"], default="all", help="Earnings period")
|
||||
|
||||
# Marketplace commands
|
||||
marketplace_parser = subparsers.add_parser("marketplace", help="Manage marketplace offers")
|
||||
marketplace_subparsers = marketplace_parser.add_subparsers(dest="marketplace_action", help="Marketplace actions")
|
||||
|
||||
# Marketplace list
|
||||
market_list_parser = marketplace_subparsers.add_parser("list", help="List marketplace offers")
|
||||
market_list_parser.add_argument("--miner-id", help="Filter by miner ID")
|
||||
market_list_parser.add_argument("--region", help="Filter by region")
|
||||
|
||||
# Marketplace create
|
||||
market_create_parser = marketplace_subparsers.add_parser("create", help="Create marketplace offer")
|
||||
market_create_parser.add_argument("--miner-id", required=True, help="Miner identifier")
|
||||
market_create_parser.add_argument("--price", type=float, required=True, help="Offer price per hour")
|
||||
market_create_parser.add_argument("--capacity", type=int, default=1, help="Available capacity")
|
||||
market_create_parser.add_argument("--region", help="Geographic region")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.action:
|
||||
parser.print_help()
|
||||
return
|
||||
|
||||
# Initialize action variable
|
||||
action = args.action
|
||||
|
||||
# Prepare kwargs for the dispatcher
|
||||
kwargs = {
|
||||
"coordinator_url": args.coordinator_url,
|
||||
"api_key": args.api_key
|
||||
}
|
||||
|
||||
# Add action-specific arguments
|
||||
if args.action == "register":
|
||||
kwargs.update({
|
||||
"miner_id": args.miner_id,
|
||||
"wallet": args.wallet,
|
||||
"capabilities": args.capabilities,
|
||||
"gpu_memory": args.gpu_memory,
|
||||
"models": args.models,
|
||||
"pricing": args.pricing,
|
||||
"concurrency": args.concurrency,
|
||||
"region": args.region
|
||||
})
|
||||
|
||||
elif args.action == "status":
|
||||
kwargs["miner_id"] = args.miner_id
|
||||
|
||||
elif args.action == "heartbeat":
|
||||
kwargs.update({
|
||||
"miner_id": args.miner_id,
|
||||
"inflight": args.inflight,
|
||||
"status": args.status
|
||||
})
|
||||
|
||||
elif args.action == "poll":
|
||||
kwargs.update({
|
||||
"miner_id": args.miner_id,
|
||||
"max_wait": args.max_wait,
|
||||
"auto_execute": args.auto_execute
|
||||
})
|
||||
|
||||
elif args.action == "result":
|
||||
kwargs.update({
|
||||
"job_id": args.job_id,
|
||||
"miner_id": args.miner_id,
|
||||
"result": args.result,
|
||||
"result_file": args.result_file,
|
||||
"success": args.success,
|
||||
"duration": args.duration
|
||||
})
|
||||
|
||||
elif args.action == "update":
|
||||
kwargs.update({
|
||||
"miner_id": args.miner_id,
|
||||
"capabilities": args.capabilities,
|
||||
"gpu_memory": args.gpu_memory,
|
||||
"models": args.models,
|
||||
"pricing": args.pricing,
|
||||
"concurrency": args.concurrency,
|
||||
"region": args.region,
|
||||
"wallet": args.wallet
|
||||
})
|
||||
|
||||
elif args.action == "earnings":
|
||||
kwargs.update({
|
||||
"miner_id": args.miner_id,
|
||||
"period": args.period
|
||||
})
|
||||
|
||||
elif args.action == "marketplace":
|
||||
action = args.action
|
||||
if args.marketplace_action == "list":
|
||||
kwargs.update({
|
||||
"miner_id": getattr(args, 'miner_id', None),
|
||||
"region": getattr(args, 'region', None)
|
||||
})
|
||||
action = "marketplace_list"
|
||||
elif args.marketplace_action == "create":
|
||||
kwargs.update({
|
||||
"miner_id": args.miner_id,
|
||||
"price": args.price,
|
||||
"capacity": args.capacity,
|
||||
"region": getattr(args, 'region', None)
|
||||
})
|
||||
action = "marketplace_create"
|
||||
else:
|
||||
print("❌ Unknown marketplace action")
|
||||
return
|
||||
|
||||
result = miner_cli_dispatcher(action, **kwargs)
|
||||
|
||||
# Display results
|
||||
if result:
|
||||
print("\n" + "="*60)
|
||||
print(f"🤖 AITBC Miner Management - {action.upper()}")
|
||||
print("="*60)
|
||||
|
||||
if "status" in result:
|
||||
print(f"Status: {result['status']}")
|
||||
|
||||
if result.get("status", "").startswith("✅"):
|
||||
# Success - show details
|
||||
for key, value in result.items():
|
||||
if key not in ["action", "status"]:
|
||||
if isinstance(value, (dict, list)):
|
||||
print(f"{key}:")
|
||||
if isinstance(value, dict):
|
||||
for k, v in value.items():
|
||||
print(f" {k}: {v}")
|
||||
else:
|
||||
for item in value:
|
||||
print(f" - {item}")
|
||||
else:
|
||||
print(f"{key}: {value}")
|
||||
else:
|
||||
# Error or info - show all relevant fields
|
||||
for key, value in result.items():
|
||||
if key != "action":
|
||||
print(f"{key}: {value}")
|
||||
|
||||
print("="*60)
|
||||
else:
|
||||
print("❌ No response from server")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
505
cli/miner_management.py
Normal file
505
cli/miner_management.py
Normal file
@@ -0,0 +1,505 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
AITBC Miner Management Module
|
||||
Complete command-line interface for AI compute miner operations including:
|
||||
- Miner Registration
|
||||
- Status Management
|
||||
- Job Polling & Execution
|
||||
- Marketplace Integration
|
||||
- Payment Management
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
import requests
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
# Default configuration
|
||||
DEFAULT_COORDINATOR_URL = "http://localhost:8000"
|
||||
DEFAULT_API_KEY = "miner_prod_key_use_real_value"
|
||||
|
||||
|
||||
def register_miner(
|
||||
miner_id: str,
|
||||
wallet: str,
|
||||
api_key: str = DEFAULT_API_KEY,
|
||||
coordinator_url: str = DEFAULT_COORDINATOR_URL,
|
||||
capabilities: Optional[str] = None,
|
||||
gpu_memory: Optional[int] = None,
|
||||
models: Optional[list] = None,
|
||||
pricing: Optional[float] = None,
|
||||
concurrency: int = 1,
|
||||
region: Optional[str] = None
|
||||
) -> Optional[Dict]:
|
||||
"""Register miner as AI compute provider"""
|
||||
try:
|
||||
headers = {
|
||||
"X-Api-Key": api_key,
|
||||
"X-Miner-ID": miner_id,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
# Build capabilities from arguments
|
||||
caps = {}
|
||||
|
||||
if gpu_memory:
|
||||
caps["gpu_memory"] = gpu_memory
|
||||
caps["gpu_memory_gb"] = gpu_memory
|
||||
if models:
|
||||
caps["models"] = models
|
||||
caps["supported_models"] = models
|
||||
if pricing:
|
||||
caps["pricing_per_hour"] = pricing
|
||||
caps["price_per_hour"] = pricing
|
||||
caps["gpu"] = "AI-GPU"
|
||||
caps["gpu_count"] = 1
|
||||
caps["cuda_version"] = "12.0"
|
||||
|
||||
# Override with capabilities JSON if provided
|
||||
if capabilities:
|
||||
caps.update(json.loads(capabilities))
|
||||
|
||||
payload = {
|
||||
"wallet_address": wallet,
|
||||
"capabilities": caps,
|
||||
"concurrency": concurrency,
|
||||
"region": region
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{coordinator_url}/v1/miners/register",
|
||||
headers=headers,
|
||||
json=payload
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
return {
|
||||
"action": "register",
|
||||
"miner_id": miner_id,
|
||||
"status": "✅ Registered successfully",
|
||||
"session_token": result.get("session_token"),
|
||||
"coordinator_url": coordinator_url,
|
||||
"capabilities": caps
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"action": "register",
|
||||
"status": "❌ Registration failed",
|
||||
"error": response.text,
|
||||
"status_code": response.status_code
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {"action": "register", "status": f"❌ Error: {str(e)}"}
|
||||
|
||||
|
||||
def get_miner_status(
|
||||
miner_id: str,
|
||||
api_key: str = DEFAULT_API_KEY,
|
||||
coordinator_url: str = DEFAULT_COORDINATOR_URL
|
||||
) -> Optional[Dict]:
|
||||
"""Get miner status and statistics"""
|
||||
try:
|
||||
# Use admin API key to get miner status
|
||||
admin_api_key = api_key.replace("miner_", "admin_")
|
||||
headers = {"X-Api-Key": admin_api_key}
|
||||
|
||||
response = requests.get(
|
||||
f"{coordinator_url}/v1/admin/miners",
|
||||
headers=headers
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
miners = response.json().get("items", [])
|
||||
miner_info = next((m for m in miners if m["miner_id"] == miner_id), None)
|
||||
|
||||
if miner_info:
|
||||
return {
|
||||
"action": "status",
|
||||
"miner_id": miner_id,
|
||||
"status": f"✅ {miner_info['status']}",
|
||||
"inflight": miner_info["inflight"],
|
||||
"concurrency": miner_info["concurrency"],
|
||||
"region": miner_info["region"],
|
||||
"last_heartbeat": miner_info["last_heartbeat"],
|
||||
"jobs_completed": miner_info["jobs_completed"],
|
||||
"jobs_failed": miner_info["jobs_failed"],
|
||||
"average_job_duration_ms": miner_info["average_job_duration_ms"],
|
||||
"success_rate": (
|
||||
miner_info["jobs_completed"] /
|
||||
max(1, miner_info["jobs_completed"] + miner_info["jobs_failed"]) * 100
|
||||
)
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"action": "status",
|
||||
"miner_id": miner_id,
|
||||
"status": "❌ Miner not found"
|
||||
}
|
||||
else:
|
||||
return {"action": "status", "status": "❌ Failed to get status", "error": response.text}
|
||||
|
||||
except Exception as e:
|
||||
return {"action": "status", "status": f"❌ Error: {str(e)}"}
|
||||
|
||||
|
||||
def send_heartbeat(
|
||||
miner_id: str,
|
||||
api_key: str = DEFAULT_API_KEY,
|
||||
coordinator_url: str = DEFAULT_COORDINATOR_URL,
|
||||
inflight: int = 0,
|
||||
status: str = "ONLINE"
|
||||
) -> Optional[Dict]:
|
||||
"""Send miner heartbeat"""
|
||||
try:
|
||||
headers = {
|
||||
"X-Api-Key": api_key,
|
||||
"X-Miner-ID": miner_id,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
payload = {
|
||||
"inflight": inflight,
|
||||
"status": status,
|
||||
"metadata": {
|
||||
"timestamp": time.time(),
|
||||
"version": "1.0.0",
|
||||
"system_info": "AI Compute Miner"
|
||||
}
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{coordinator_url}/v1/miners/heartbeat",
|
||||
headers=headers,
|
||||
json=payload
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
return {
|
||||
"action": "heartbeat",
|
||||
"miner_id": miner_id,
|
||||
"status": "✅ Heartbeat sent successfully",
|
||||
"inflight": inflight,
|
||||
"miner_status": status
|
||||
}
|
||||
else:
|
||||
return {"action": "heartbeat", "status": "❌ Heartbeat failed", "error": response.text}
|
||||
|
||||
except Exception as e:
|
||||
return {"action": "heartbeat", "status": f"❌ Error: {str(e)}"}
|
||||
|
||||
|
||||
def poll_jobs(
|
||||
miner_id: str,
|
||||
api_key: str = DEFAULT_API_KEY,
|
||||
coordinator_url: str = DEFAULT_COORDINATOR_URL,
|
||||
max_wait: int = 30,
|
||||
auto_execute: bool = False
|
||||
) -> Optional[Dict]:
|
||||
"""Poll for available jobs"""
|
||||
try:
|
||||
headers = {
|
||||
"X-Api-Key": api_key,
|
||||
"X-Miner-ID": miner_id,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
payload = {"max_wait_seconds": max_wait}
|
||||
|
||||
response = requests.post(
|
||||
f"{coordinator_url}/v1/miners/poll",
|
||||
headers=headers,
|
||||
json=payload
|
||||
)
|
||||
|
||||
if response.status_code == 200 and response.content:
|
||||
job = response.json()
|
||||
result = {
|
||||
"action": "poll",
|
||||
"miner_id": miner_id,
|
||||
"status": "✅ Job assigned",
|
||||
"job_id": job.get("job_id"),
|
||||
"payload": job.get("payload"),
|
||||
"constraints": job.get("constraints"),
|
||||
"assigned_at": time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
}
|
||||
|
||||
if auto_execute:
|
||||
result["auto_execution"] = "🤖 Job execution would start here"
|
||||
result["execution_status"] = "Ready to execute"
|
||||
|
||||
return result
|
||||
elif response.status_code == 204:
|
||||
return {
|
||||
"action": "poll",
|
||||
"miner_id": miner_id,
|
||||
"status": "⏸️ No jobs available",
|
||||
"message": "No jobs in queue"
|
||||
}
|
||||
else:
|
||||
return {"action": "poll", "status": "❌ Poll failed", "error": response.text}
|
||||
|
||||
except Exception as e:
|
||||
return {"action": "poll", "status": f"❌ Error: {str(e)}"}
|
||||
|
||||
|
||||
def submit_job_result(
|
||||
job_id: str,
|
||||
miner_id: str,
|
||||
result: str,
|
||||
api_key: str = DEFAULT_API_KEY,
|
||||
coordinator_url: str = DEFAULT_COORDINATOR_URL,
|
||||
success: bool = True,
|
||||
duration: Optional[int] = None,
|
||||
result_file: Optional[str] = None
|
||||
) -> Optional[Dict]:
|
||||
"""Submit job result"""
|
||||
try:
|
||||
headers = {
|
||||
"X-Api-Key": api_key,
|
||||
"X-Miner-ID": miner_id,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
# Load result from file if specified
|
||||
if result_file:
|
||||
with open(result_file, 'r') as f:
|
||||
result = f.read()
|
||||
|
||||
payload = {
|
||||
"result": result,
|
||||
"success": success,
|
||||
"metrics": {
|
||||
"duration_ms": duration,
|
||||
"completed_at": time.time()
|
||||
}
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{coordinator_url}/v1/miners/{job_id}/result",
|
||||
headers=headers,
|
||||
json=payload
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
return {
|
||||
"action": "result",
|
||||
"job_id": job_id,
|
||||
"miner_id": miner_id,
|
||||
"status": "✅ Result submitted successfully",
|
||||
"success": success,
|
||||
"duration_ms": duration
|
||||
}
|
||||
else:
|
||||
return {"action": "result", "status": "❌ Result submission failed", "error": response.text}
|
||||
|
||||
except Exception as e:
|
||||
return {"action": "result", "status": f"❌ Error: {str(e)}"}
|
||||
|
||||
|
||||
def update_capabilities(
|
||||
miner_id: str,
|
||||
api_key: str = DEFAULT_API_KEY,
|
||||
coordinator_url: str = DEFAULT_COORDINATOR_URL,
|
||||
capabilities: Optional[str] = None,
|
||||
gpu_memory: Optional[int] = None,
|
||||
models: Optional[list] = None,
|
||||
pricing: Optional[float] = None,
|
||||
concurrency: Optional[int] = None,
|
||||
region: Optional[str] = None,
|
||||
wallet: Optional[str] = None
|
||||
) -> Optional[Dict]:
|
||||
"""Update miner capabilities"""
|
||||
try:
|
||||
headers = {
|
||||
"X-Api-Key": api_key,
|
||||
"X-Miner-ID": miner_id,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
# Build capabilities from arguments
|
||||
caps = {}
|
||||
if gpu_memory:
|
||||
caps["gpu_memory"] = gpu_memory
|
||||
caps["gpu_memory_gb"] = gpu_memory
|
||||
if models:
|
||||
caps["models"] = models
|
||||
caps["supported_models"] = models
|
||||
if pricing:
|
||||
caps["pricing_per_hour"] = pricing
|
||||
caps["price_per_hour"] = pricing
|
||||
|
||||
# Override with capabilities JSON if provided
|
||||
if capabilities:
|
||||
caps.update(json.loads(capabilities))
|
||||
|
||||
payload = {
|
||||
"capabilities": caps,
|
||||
"concurrency": concurrency,
|
||||
"region": region
|
||||
}
|
||||
|
||||
if wallet:
|
||||
payload["wallet_address"] = wallet
|
||||
|
||||
response = requests.put(
|
||||
f"{coordinator_url}/v1/miners/{miner_id}/capabilities",
|
||||
headers=headers,
|
||||
json=payload
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
return {
|
||||
"action": "update",
|
||||
"miner_id": miner_id,
|
||||
"status": "✅ Capabilities updated successfully",
|
||||
"updated_capabilities": caps
|
||||
}
|
||||
else:
|
||||
return {"action": "update", "status": "❌ Update failed", "error": response.text}
|
||||
|
||||
except Exception as e:
|
||||
return {"action": "update", "status": f"❌ Error: {str(e)}"}
|
||||
|
||||
|
||||
def check_earnings(
|
||||
miner_id: str,
|
||||
api_key: str = DEFAULT_API_KEY,
|
||||
coordinator_url: str = DEFAULT_COORDINATOR_URL,
|
||||
period: str = "all"
|
||||
) -> Optional[Dict]:
|
||||
"""Check miner earnings (placeholder for payment integration)"""
|
||||
try:
|
||||
# This would integrate with payment system when implemented
|
||||
return {
|
||||
"action": "earnings",
|
||||
"miner_id": miner_id,
|
||||
"period": period,
|
||||
"status": "📊 Earnings calculation",
|
||||
"total_earnings": 0.0,
|
||||
"jobs_completed": 0,
|
||||
"average_payment": 0.0,
|
||||
"note": "Payment integration coming soon"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {"action": "earnings", "status": f"❌ Error: {str(e)}"}
|
||||
|
||||
|
||||
def list_marketplace_offers(
|
||||
miner_id: Optional[str] = None,
|
||||
region: Optional[str] = None,
|
||||
api_key: str = DEFAULT_API_KEY,
|
||||
coordinator_url: str = DEFAULT_COORDINATOR_URL
|
||||
) -> Optional[Dict]:
|
||||
"""List marketplace offers"""
|
||||
try:
|
||||
admin_headers = {"X-Api-Key": api_key.replace("miner_", "admin_")}
|
||||
|
||||
params = {}
|
||||
if region:
|
||||
params["region"] = region
|
||||
|
||||
response = requests.get(
|
||||
f"{coordinator_url}/v1/marketplace/miner-offers",
|
||||
headers=admin_headers,
|
||||
params=params
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
offers = response.json()
|
||||
|
||||
# Filter by miner if specified
|
||||
if miner_id:
|
||||
offers = [o for o in offers if miner_id in str(o).lower()]
|
||||
|
||||
return {
|
||||
"action": "marketplace_list",
|
||||
"status": "✅ Offers retrieved",
|
||||
"offers": offers,
|
||||
"count": len(offers),
|
||||
"region_filter": region,
|
||||
"miner_filter": miner_id
|
||||
}
|
||||
else:
|
||||
return {"action": "marketplace_list", "status": "❌ Failed to get offers", "error": response.text}
|
||||
|
||||
except Exception as e:
|
||||
return {"action": "marketplace_list", "status": f"❌ Error: {str(e)}"}
|
||||
|
||||
|
||||
def create_marketplace_offer(
|
||||
miner_id: str,
|
||||
price: float,
|
||||
api_key: str = DEFAULT_API_KEY,
|
||||
coordinator_url: str = DEFAULT_COORDINATOR_URL,
|
||||
capacity: int = 1,
|
||||
region: Optional[str] = None
|
||||
) -> Optional[Dict]:
|
||||
"""Create marketplace offer"""
|
||||
try:
|
||||
admin_headers = {"X-Api-Key": api_key.replace("miner_", "admin_")}
|
||||
|
||||
payload = {
|
||||
"miner_id": miner_id,
|
||||
"price": price,
|
||||
"capacity": capacity,
|
||||
"region": region
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{coordinator_url}/v1/marketplace/offers",
|
||||
headers=admin_headers,
|
||||
json=payload
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
return {
|
||||
"action": "marketplace_create",
|
||||
"miner_id": miner_id,
|
||||
"status": "✅ Offer created successfully",
|
||||
"price": price,
|
||||
"capacity": capacity,
|
||||
"region": region
|
||||
}
|
||||
else:
|
||||
return {"action": "marketplace_create", "status": "❌ Offer creation failed", "error": response.text}
|
||||
|
||||
except Exception as e:
|
||||
return {"action": "marketplace_create", "status": f"❌ Error: {str(e)}"}
|
||||
|
||||
|
||||
# Main function for CLI integration
|
||||
def miner_cli_dispatcher(action: str, **kwargs) -> Optional[Dict]:
|
||||
"""Main dispatcher for miner management CLI commands"""
|
||||
|
||||
actions = {
|
||||
"register": register_miner,
|
||||
"status": get_miner_status,
|
||||
"heartbeat": send_heartbeat,
|
||||
"poll": poll_jobs,
|
||||
"result": submit_job_result,
|
||||
"update": update_capabilities,
|
||||
"earnings": check_earnings,
|
||||
"marketplace_list": list_marketplace_offers,
|
||||
"marketplace_create": create_marketplace_offer
|
||||
}
|
||||
|
||||
if action in actions:
|
||||
return actions[action](**kwargs)
|
||||
else:
|
||||
return {
|
||||
"action": action,
|
||||
"status": f"❌ Unknown action. Available: {', '.join(actions.keys())}"
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Test the module
|
||||
print("🚀 AITBC Miner Management Module")
|
||||
print("Available functions:")
|
||||
for func in [register_miner, get_miner_status, send_heartbeat, poll_jobs,
|
||||
submit_job_result, update_capabilities, check_earnings,
|
||||
list_marketplace_offers, create_marketplace_offer]:
|
||||
print(f" - {func.__name__}")
|
||||
Reference in New Issue
Block a user