- Deleted chains.json, discovery.json, islands.json static configuration files - Removed /agent/join/ait-mainnet.json configuration guide - Updated index.html to show only mainnet island, removed testnet card - Changed note from "Agent-First Design" to "Live Data Only" emphasizing real-time RPC queries - Added "Live data from RPC" indicator to mainnet island card - All agent endpoints now serve dynamic data from blockchain
318 lines
11 KiB
Python
318 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
AITBC Agent Live API
|
|
Real-time dynamic endpoints that query blockchain RPC and serve fresh data
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
from typing import Dict, Any, Optional
|
|
|
|
# Try to import fastapi, fallback to flask
|
|
try:
|
|
from fastapi import FastAPI, HTTPException
|
|
from fastapi.responses import JSONResponse
|
|
import uvicorn
|
|
HAS_FASTAPI = True
|
|
except ImportError:
|
|
try:
|
|
from flask import Flask, jsonify, abort
|
|
HAS_FLASK = True
|
|
except ImportError:
|
|
print("Error: Neither FastAPI nor Flask installed")
|
|
sys.exit(1)
|
|
|
|
import urllib.request
|
|
import urllib.error
|
|
import ssl
|
|
|
|
# Configuration
|
|
RPC_URL = os.getenv("AITBC_RPC_URL", "http://localhost:8006/rpc")
|
|
NODE_ID = os.getenv("NODE_ID", "aitbc")
|
|
ISLAND_ID = os.getenv("ISLAND_ID", "ait-mainnet-island")
|
|
CHAIN_ID = os.getenv("CHAIN_ID", "ait-mainnet")
|
|
NODE_ROLE = os.getenv("NODE_ROLE", "hub")
|
|
|
|
# SSL context that doesn't verify certs (for local dev)
|
|
ssl_context = ssl.create_default_context()
|
|
ssl_context.check_hostname = False
|
|
ssl_context.verify_mode = ssl.CERT_NONE
|
|
|
|
|
|
def rpc_query(method: str, params: Optional[list] = None) -> Optional[Dict]:
|
|
"""Query the blockchain RPC endpoint"""
|
|
try:
|
|
url = f"{RPC_URL}/{method}"
|
|
req = urllib.request.Request(
|
|
url,
|
|
headers={
|
|
"Accept": "application/json",
|
|
"Content-Type": "application/json"
|
|
}
|
|
)
|
|
with urllib.request.urlopen(req, timeout=5, context=ssl_context) as response:
|
|
return json.loads(response.read().decode())
|
|
except Exception as e:
|
|
print(f"RPC query failed: {e}", file=sys.stderr)
|
|
return None
|
|
|
|
|
|
def get_live_island_data() -> Dict[str, Any]:
|
|
"""Get real-time island data from RPC"""
|
|
# Query chain head for current block info
|
|
head = rpc_query("head")
|
|
info = rpc_query("info")
|
|
islands_rpc = rpc_query("islands")
|
|
|
|
# Get peer count from island manager if available
|
|
peer_count = 0
|
|
if islands_rpc and isinstance(islands_rpc, dict) and "islands" in islands_rpc:
|
|
for island in islands_rpc["islands"]:
|
|
if island.get("island_id") == ISLAND_ID:
|
|
peer_count = island.get("peer_count", 0)
|
|
break
|
|
|
|
block_height = head.get("height", 0) if head else 0
|
|
block_hash = head.get("hash", "unknown") if head else "unknown"
|
|
timestamp = head.get("timestamp", datetime.now(timezone.utc).isoformat()) if head else datetime.now(timezone.utc).isoformat()
|
|
|
|
return {
|
|
"islands": [
|
|
{
|
|
"island_id": ISLAND_ID,
|
|
"island_name": "AIT Mainnet" if CHAIN_ID == "ait-mainnet" else "AIT Testnet",
|
|
"chain_id": CHAIN_ID,
|
|
"status": "active",
|
|
"role": NODE_ROLE,
|
|
"chain_info": {
|
|
"block_time": 5,
|
|
"consensus": "proof_of_authority",
|
|
"network_id": 1337,
|
|
"current_height": block_height,
|
|
"current_hash": block_hash,
|
|
"last_update": timestamp
|
|
},
|
|
"endpoints": {
|
|
"rpc": [
|
|
{"url": f"http://{NODE_ID}:8006", "node_id": NODE_ID, "role": NODE_ROLE},
|
|
{"url": f"http://{'aitbc1' if NODE_ID == 'aitbc' else 'aitbc'}:8006",
|
|
"node_id": 'aitbc1' if NODE_ID == 'aitbc' else 'aitbc',
|
|
"role": "follower" if NODE_ROLE == "hub" else "hub"}
|
|
],
|
|
"p2p": [
|
|
{"address": f"{NODE_ID}:7070", "node_id": NODE_ID},
|
|
{"address": f"{'aitbc1' if NODE_ID == 'aitbc' else 'aitbc'}:7070",
|
|
"node_id": 'aitbc1' if NODE_ID == 'aitbc' else 'aitbc'}
|
|
]
|
|
},
|
|
"stats": {
|
|
"peer_count": peer_count,
|
|
"block_height": block_height,
|
|
"last_block_hash": block_hash,
|
|
"last_block_time": timestamp
|
|
},
|
|
"this_node": {
|
|
"node_id": NODE_ID,
|
|
"role": NODE_ROLE,
|
|
"is_hub": NODE_ROLE == "hub",
|
|
"block_production_chains": [CHAIN_ID] if NODE_ROLE == "hub" else [],
|
|
"enable_block_production": NODE_ROLE == "hub"
|
|
}
|
|
}
|
|
],
|
|
"_meta": {
|
|
"generated_at": datetime.now(timezone.utc).isoformat(),
|
|
"format_version": "1.0",
|
|
"total_islands": 1,
|
|
"active_islands": 1,
|
|
"data_source": "live_rpc",
|
|
"note": "This endpoint shows real-time data from the blockchain RPC"
|
|
}
|
|
}
|
|
|
|
|
|
def get_live_chain_data() -> Dict[str, Any]:
|
|
"""Get real-time chain data from RPC"""
|
|
head = rpc_query("head")
|
|
info = rpc_query("info")
|
|
|
|
block_height = head.get("height", 0) if head else 0
|
|
block_hash = head.get("hash", "unknown") if head else "unknown"
|
|
timestamp = head.get("timestamp", datetime.now(timezone.utc).isoformat()) if head else datetime.now(timezone.utc).isoformat()
|
|
tx_count = head.get("tx_count", 0) if head else 0
|
|
|
|
return {
|
|
"chains": [
|
|
{
|
|
"chain_id": CHAIN_ID,
|
|
"name": "AIT Mainnet" if CHAIN_ID == "ait-mainnet" else "AIT Testnet",
|
|
"island_id": ISLAND_ID,
|
|
"type": "production" if CHAIN_ID == "ait-mainnet" else "test",
|
|
"status": "active",
|
|
"live_stats": {
|
|
"current_height": block_height,
|
|
"current_hash": block_hash,
|
|
"last_block_time": timestamp,
|
|
"tx_count_last_block": tx_count,
|
|
"queried_at": datetime.now(timezone.utc).isoformat()
|
|
},
|
|
"config": {
|
|
"block_time": info.get("block_time", 5) if info else 5,
|
|
"consensus": "proof_of_authority",
|
|
"network_id": info.get("network_id", 1337) if info else 1337
|
|
},
|
|
"endpoints": {
|
|
"rpc": [
|
|
f"http://{NODE_ID}:8006/rpc",
|
|
f"http://{'aitbc1' if NODE_ID == 'aitbc' else 'aitbc'}:8006/rpc"
|
|
],
|
|
"head": "/rpc/head",
|
|
"info": "/rpc/info",
|
|
"supply": "/rpc/supply"
|
|
},
|
|
"join": {
|
|
"guide": f"/agent/join/{CHAIN_ID}.json"
|
|
}
|
|
}
|
|
],
|
|
"_meta": {
|
|
"generated_at": datetime.now(timezone.utc).isoformat(),
|
|
"format_version": "1.0",
|
|
"total_chains": 1,
|
|
"active_chains": 1,
|
|
"data_source": "live_rpc"
|
|
}
|
|
}
|
|
|
|
|
|
def get_live_discovery() -> Dict[str, Any]:
|
|
"""Get live discovery data"""
|
|
head = rpc_query("head")
|
|
health = rpc_query("health") or {}
|
|
|
|
block_height = head.get("height", 0) if head else 0
|
|
|
|
return {
|
|
"network": {
|
|
"name": "AITBC",
|
|
"version": "1.0.0",
|
|
"description": "AI-powered blockchain platform",
|
|
"live_status": {
|
|
"current_height": block_height,
|
|
"node_health": health.get("status", "unknown"),
|
|
"last_update": datetime.now(timezone.utc).isoformat()
|
|
},
|
|
"apis": {
|
|
"rpc": {
|
|
"url": f"http://{NODE_ID}:8006/rpc",
|
|
"documentation": "/agent/openapi.json"
|
|
},
|
|
"agent": {
|
|
"discovery": "/agent/discovery.json",
|
|
"islands": "/agent/islands.json",
|
|
"chains": "/agent/chains.json",
|
|
"health": "/agent/health"
|
|
}
|
|
},
|
|
"islands": [
|
|
{
|
|
"island_id": ISLAND_ID,
|
|
"name": "AIT Mainnet" if CHAIN_ID == "ait-mainnet" else "AIT Testnet",
|
|
"chain_id": CHAIN_ID,
|
|
"current_height": block_height,
|
|
"status": "active",
|
|
"join_guide": f"/agent/join/{CHAIN_ID}.json",
|
|
"note": f"This node ({NODE_ID}) is the HUB for this island"
|
|
}
|
|
]
|
|
},
|
|
"this_node": {
|
|
"node_id": NODE_ID,
|
|
"role": NODE_ROLE,
|
|
"is_hub": NODE_ROLE == "hub",
|
|
"block_production": NODE_ROLE == "hub",
|
|
"chains": [CHAIN_ID] if NODE_ROLE == "hub" else [],
|
|
"island_memberships": [ISLAND_ID],
|
|
"live_data": True
|
|
},
|
|
"_meta": {
|
|
"generated_at": datetime.now(timezone.utc).isoformat(),
|
|
"format_version": "1.1",
|
|
"data_source": "live_rpc"
|
|
}
|
|
}
|
|
|
|
|
|
# Create app based on available framework
|
|
if HAS_FASTAPI:
|
|
app = FastAPI(title="AITBC Agent Live API")
|
|
|
|
@app.get("/agent/islands.json")
|
|
async def live_islands():
|
|
return JSONResponse(content=get_live_island_data())
|
|
|
|
@app.get("/agent/chains.json")
|
|
async def live_chains():
|
|
return JSONResponse(content=get_live_chain_data())
|
|
|
|
@app.get("/agent/discovery.json")
|
|
async def live_discovery():
|
|
return JSONResponse(content=get_live_discovery())
|
|
|
|
@app.get("/agent/health")
|
|
async def live_health():
|
|
head = rpc_query("head")
|
|
return JSONResponse(content={
|
|
"status": "healthy" if head else "unhealthy",
|
|
"node_id": NODE_ID,
|
|
"chain_id": CHAIN_ID,
|
|
"current_height": head.get("height", 0) if head else 0,
|
|
"timestamp": datetime.now(timezone.utc).isoformat()
|
|
})
|
|
|
|
elif HAS_FLASK:
|
|
app = Flask(__name__)
|
|
|
|
@app.route("/agent/islands.json")
|
|
def live_islands():
|
|
return jsonify(get_live_island_data())
|
|
|
|
@app.route("/agent/chains.json")
|
|
def live_chains():
|
|
return jsonify(get_live_chain_data())
|
|
|
|
@app.route("/agent/discovery.json")
|
|
def live_discovery():
|
|
return jsonify(get_live_discovery())
|
|
|
|
@app.route("/agent/health")
|
|
def live_health():
|
|
head = rpc_query("head")
|
|
return jsonify({
|
|
"status": "healthy" if head else "unhealthy",
|
|
"node_id": NODE_ID,
|
|
"chain_id": CHAIN_ID,
|
|
"current_height": head.get("height", 0) if head else 0,
|
|
"timestamp": datetime.now(timezone.utc).isoformat()
|
|
})
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import argparse
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--port", type=int, default=8080)
|
|
parser.add_argument("--host", default="127.0.0.1")
|
|
args = parser.parse_args()
|
|
|
|
print(f"Starting AITBC Agent Live API on {args.host}:{args.port}")
|
|
print(f"RPC URL: {RPC_URL}")
|
|
print(f"Node: {NODE_ID} | Chain: {CHAIN_ID} | Role: {NODE_ROLE}")
|
|
|
|
if HAS_FASTAPI:
|
|
uvicorn.run(app, host=args.host, port=args.port)
|
|
elif HAS_FLASK:
|
|
app.run(host=args.host, port=args.port)
|