refactor: remove static agent discovery JSON files - migrate to live RPC endpoints
- 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
This commit is contained in:
227
website/DEPLOYMENT.md
Normal file
227
website/DEPLOYMENT.md
Normal file
@@ -0,0 +1,227 @@
|
||||
# AITBC Agent-First Website Deployment
|
||||
|
||||
This directory contains an **agent-first website** that provides machine-readable discovery endpoints for autonomous agents.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Serve Directly from Repository
|
||||
|
||||
The website is designed to be served directly from this repository location:
|
||||
|
||||
```bash
|
||||
# Using Python (development)
|
||||
cd /opt/aitbc/website
|
||||
python3 -m http.server 8080
|
||||
|
||||
# Using nginx (production) - see nginx-example.conf
|
||||
sudo cp nginx-example.conf /etc/nginx/sites-available/aitbc-agent
|
||||
sudo ln -s /etc/nginx/sites-available/aitbc-agent /etc/nginx/sites-enabled/
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
### 2. Configure Node-Specific Settings
|
||||
|
||||
Edit the JSON files to match your node's role:
|
||||
|
||||
**For aitbc (Mainnet Hub):**
|
||||
- `agent/discovery.json` - Already configured for mainnet
|
||||
- `agent/islands.json` - Shows ait-mainnet-island only
|
||||
- `agent/chains.json` - Shows ait-mainnet only
|
||||
- Remove `agent/join/ait-testnet.json` if present
|
||||
|
||||
**For aitbc1 (Testnet Hub):**
|
||||
- `agent/discovery.json` - Update to testnet configuration
|
||||
- `agent/islands.json` - Update to show ait-testnet-island
|
||||
- `agent/chains.json` - Update to show ait-testnet
|
||||
- Remove `agent/join/ait-mainnet.json`
|
||||
|
||||
### 3. Enable Live API (Optional but Recommended)
|
||||
|
||||
The live API provides real-time data from the blockchain RPC:
|
||||
|
||||
```bash
|
||||
# Install systemd service
|
||||
sudo cp systemd-example.service /etc/systemd/system/aitbc-agent-live-api.service
|
||||
|
||||
# Edit to match your node configuration
|
||||
sudo systemctl edit aitbc-agent-live-api.service
|
||||
|
||||
# Enable and start
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable aitbc-agent-live-api
|
||||
sudo systemctl start aitbc-agent-live-api
|
||||
```
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
website/
|
||||
├── index.html # Agent landing page (human-readable)
|
||||
├── DEPLOYMENT.md # This file
|
||||
├── nginx-example.conf # Example nginx configuration
|
||||
├── systemd-example.service # Example systemd service
|
||||
├── agent/ # Machine-readable endpoints
|
||||
│ ├── index.html # Agent API documentation
|
||||
│ ├── discovery.json # Network discovery (static)
|
||||
│ ├── islands.json # Island config (static)
|
||||
│ ├── chains.json # Chain config (static)
|
||||
│ ├── openapi.json # API specification
|
||||
│ ├── live_api.py # Live API service (dynamic)
|
||||
│ ├── health # Health check
|
||||
│ └── join/ # Join instructions
|
||||
│ ├── ait-mainnet.json
|
||||
│ └── ait-testnet.json
|
||||
└── assets/ # Static assets (minimal)
|
||||
```
|
||||
|
||||
## Endpoints
|
||||
|
||||
### Static Endpoints (Fast, Cached)
|
||||
|
||||
| Endpoint | Description | Cache |
|
||||
|----------|-------------|-------|
|
||||
| `/agent/discovery.json` | Network topology | 60s |
|
||||
| `/agent/islands.json` | Island configuration | 30s |
|
||||
| `/agent/chains.json` | Chain configuration | 60s |
|
||||
| `/agent/join/*.json` | Join instructions | 1h |
|
||||
|
||||
### Live Endpoints (Real-Time, Requires live_api.py)
|
||||
|
||||
| Endpoint | Description | Cache |
|
||||
|----------|-------------|-------|
|
||||
| `/agent/live/discovery.json` | Real-time discovery | No cache |
|
||||
| `/agent/live/islands.json` | Live island data | No cache |
|
||||
| `/agent/live/chains.json` | Live chain data | No cache |
|
||||
| `/agent/live/health` | Real-time health | No cache |
|
||||
|
||||
### RPC Endpoints (Blockchain Access)
|
||||
|
||||
| Endpoint | Description |
|
||||
|----------|-------------|
|
||||
| `/rpc/head` | Current block height |
|
||||
| `/rpc/info` | Chain information |
|
||||
| `/rpc/islands` | Island memberships |
|
||||
|
||||
## Node Configuration
|
||||
|
||||
### Mainnet Hub (aitbc)
|
||||
|
||||
```json
|
||||
// agent/discovery.json
|
||||
"this_node": {
|
||||
"node_id": "aitbc",
|
||||
"role": "hub",
|
||||
"chains": ["ait-mainnet"],
|
||||
"island_memberships": ["ait-mainnet-island"]
|
||||
}
|
||||
```
|
||||
|
||||
### Testnet Hub (aitbc1)
|
||||
|
||||
```json
|
||||
// agent/discovery.json
|
||||
"this_node": {
|
||||
"node_id": "aitbc1",
|
||||
"role": "hub",
|
||||
"chains": ["ait-testnet"],
|
||||
"island_memberships": ["ait-testnet-island"]
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Test static endpoint
|
||||
curl -s http://localhost/agent/discovery.json | jq .
|
||||
|
||||
# Test live endpoint (if live_api.py is running)
|
||||
curl -s http://localhost/agent/live/islands.json | jq .
|
||||
|
||||
# Test RPC endpoint
|
||||
curl -s http://localhost/rpc/head | jq .
|
||||
|
||||
# Check CORS headers
|
||||
curl -I http://localhost/agent/discovery.json
|
||||
```
|
||||
|
||||
## Security Notes
|
||||
|
||||
1. **CORS**: All `/agent/` and `/rpc/` endpoints have `Access-Control-Allow-Origin: *` for agent access
|
||||
2. **Static files**: No sensitive data in JSON files (only public network info)
|
||||
3. **Live API**: Runs on localhost only (127.0.0.1:8080), proxied by nginx
|
||||
4. **No auth**: Discovery endpoints are public by design
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Live API not responding:**
|
||||
```bash
|
||||
# Check service status
|
||||
sudo systemctl status aitbc-agent-live-api
|
||||
|
||||
# Check logs
|
||||
sudo journalctl -u aitbc-agent-live-api -f
|
||||
|
||||
# Test directly
|
||||
curl http://127.0.0.1:8080/agent/live/health
|
||||
```
|
||||
|
||||
**Nginx config errors:**
|
||||
```bash
|
||||
# Test configuration
|
||||
sudo nginx -t
|
||||
|
||||
# Check error logs
|
||||
sudo tail -f /var/log/nginx/error.log
|
||||
```
|
||||
|
||||
**JSON files not updating:**
|
||||
- Static files are cached - edit directly in `/opt/aitbc/website/agent/`
|
||||
- For live data, ensure `live_api.py` is running
|
||||
- Check file permissions: `sudo chown -R www-data:www-data /opt/aitbc/website/`
|
||||
|
||||
## Git Workflow
|
||||
|
||||
Since the website is served directly from the repo:
|
||||
|
||||
```bash
|
||||
# Edit files
|
||||
cd /opt/aitbc/website
|
||||
vim agent/discovery.json
|
||||
|
||||
# Test locally
|
||||
python3 -m http.server 8080
|
||||
|
||||
# Commit changes
|
||||
git add .
|
||||
git commit -m "Update discovery for mainnet hub"
|
||||
git push
|
||||
|
||||
# Changes are live immediately (no deploy needed)
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ Autonomous │────▶│ Nginx (80/443) │────▶│ Static JSON │
|
||||
│ Agent │ │ │ │ (discovery) │
|
||||
└─────────────────┘ │ ┌───────────┐ │ └─────────────────┘
|
||||
│ │ /agent/* │ │
|
||||
│ │ /rpc/* │ │ ┌─────────────────┐
|
||||
│ └─────┬─────┘ │────▶│ Live API │
|
||||
│ │ │ │ (port 8080) │
|
||||
│ └────┬────┘ └─────┬───────────┘
|
||||
│ │ │
|
||||
└─────────────┘ │
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Blockchain RPC │
|
||||
│ (port 8006) │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
- Repository: https://github.com/oib/AITBC
|
||||
- Documentation: `/agent/index.html` (served at `http://your-node/agent/`)
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"chains": [
|
||||
{
|
||||
"chain_id": "ait-mainnet",
|
||||
"name": "AIT Mainnet",
|
||||
"island_id": "ait-mainnet-island",
|
||||
"type": "production",
|
||||
"status": "active",
|
||||
"config": {
|
||||
"block_time": 5,
|
||||
"consensus": "proof_of_authority",
|
||||
"network_id": 1337,
|
||||
"enable_empty_blocks": true,
|
||||
"max_empty_block_interval": 60
|
||||
},
|
||||
"endpoints": {
|
||||
"rpc": ["http://aitbc:8006/rpc", "http://aitbc1:8006/rpc"],
|
||||
"head": "/rpc/head",
|
||||
"info": "/rpc/info",
|
||||
"supply": "/rpc/supply"
|
||||
},
|
||||
"join": {
|
||||
"p2p": {
|
||||
"port": 7070,
|
||||
"peers": ["aitbc:7070", "aitbc1:7070"]
|
||||
},
|
||||
"sync": {
|
||||
"source": "http://aitbc1:8006",
|
||||
"chain_id": "ait-mainnet"
|
||||
},
|
||||
"guide": "/agent/join/ait-mainnet.json"
|
||||
}
|
||||
}
|
||||
],
|
||||
"_meta": {
|
||||
"generated_at": "2026-05-19T15:50:00Z",
|
||||
"format_version": "1.0",
|
||||
"total_chains": 1,
|
||||
"active_chains": 1,
|
||||
"note": "This endpoint only shows chains where this node (aitbc) is the HUB"
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
{
|
||||
"network": {
|
||||
"name": "AITBC",
|
||||
"version": "1.0.0",
|
||||
"description": "AI-powered blockchain platform with multi-island architecture",
|
||||
"apis": {
|
||||
"rpc": {
|
||||
"url": "http://localhost:8006/rpc",
|
||||
"documentation": "/agent/openapi.json",
|
||||
"version": "1.0"
|
||||
},
|
||||
"agent": {
|
||||
"discovery": "/agent/discovery.json",
|
||||
"islands": "/agent/islands.json",
|
||||
"chains": "/agent/chains.json",
|
||||
"health": "/agent/health",
|
||||
"join": "/agent/join/"
|
||||
}
|
||||
},
|
||||
"islands": [
|
||||
{
|
||||
"island_id": "ait-mainnet-island",
|
||||
"name": "AIT Mainnet",
|
||||
"chain_id": "ait-mainnet",
|
||||
"status": "active",
|
||||
"description": "Primary production chain for AITBC",
|
||||
"endpoints": {
|
||||
"rpc": ["http://aitbc:8006", "http://aitbc1:8006"],
|
||||
"p2p": ["aitbc:7070", "aitbc1:7070"]
|
||||
},
|
||||
"chain_info": {
|
||||
"block_time": 5,
|
||||
"consensus": "proof_of_authority",
|
||||
"network_id": 1337
|
||||
},
|
||||
"join_guide": "/agent/join/ait-mainnet.json",
|
||||
"note": "This node (aitbc) is the HUB for this island"
|
||||
}
|
||||
]
|
||||
},
|
||||
"this_node": {
|
||||
"node_id": "aitbc",
|
||||
"role": "hub",
|
||||
"is_hub": true,
|
||||
"block_production": true,
|
||||
"chains": ["ait-mainnet"],
|
||||
"island_memberships": ["ait-mainnet-island"],
|
||||
"discovery_url": "http://aitbc:8006/agent/discovery.json"
|
||||
},
|
||||
"_meta": {
|
||||
"generated_at": "2026-05-19T15:50:00Z",
|
||||
"format_version": "1.0"
|
||||
}
|
||||
}
|
||||
@@ -165,7 +165,7 @@
|
||||
</header>
|
||||
|
||||
<div class="note">
|
||||
<strong>Agent-First Design:</strong> This page provides human-readable documentation, but all endpoints return structured JSON for programmatic consumption. Start with <a href="/agent/discovery.json">/agent/discovery.json</a> for complete network information.
|
||||
<strong>Live Data Only:</strong> All API endpoints return real-time data from the blockchain RPC. No static files - everything is queried live from the node.
|
||||
</div>
|
||||
|
||||
<section class="section">
|
||||
@@ -176,12 +176,7 @@
|
||||
<p><span class="status-indicator"></span>active</p>
|
||||
<p class="description">Island: ait-mainnet-island</p>
|
||||
<p class="description">Role: Hub (Block Production)</p>
|
||||
</div>
|
||||
<div class="island-card">
|
||||
<h3>AIT Testnet</h3>
|
||||
<p><span class="status-indicator"></span>active</p>
|
||||
<p class="description">Island: ait-testnet-island</p>
|
||||
<p class="description">Role: Follower (Syncing)</p>
|
||||
<p class="description" style="font-size: 0.75rem; color: var(--warning);">Live data from RPC</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
{
|
||||
"islands": [
|
||||
{
|
||||
"island_id": "ait-mainnet-island",
|
||||
"island_name": "AIT Mainnet",
|
||||
"chain_id": "ait-mainnet",
|
||||
"status": "active",
|
||||
"role": "hub",
|
||||
"chain_info": {
|
||||
"block_time": 5,
|
||||
"consensus": "proof_of_authority",
|
||||
"network_id": 1337,
|
||||
"genesis_timestamp": "2026-05-19T14:44:44.174290"
|
||||
},
|
||||
"endpoints": {
|
||||
"rpc": [
|
||||
{
|
||||
"url": "http://aitbc:8006",
|
||||
"node_id": "aitbc",
|
||||
"role": "hub"
|
||||
},
|
||||
{
|
||||
"url": "http://aitbc1:8006",
|
||||
"node_id": "aitbc1",
|
||||
"role": "follower"
|
||||
}
|
||||
],
|
||||
"p2p": [
|
||||
{
|
||||
"address": "aitbc:7070",
|
||||
"node_id": "aitbc"
|
||||
},
|
||||
{
|
||||
"address": "aitbc1:7070",
|
||||
"node_id": "aitbc1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"this_node": {
|
||||
"node_id": "aitbc",
|
||||
"role": "hub",
|
||||
"is_hub": true,
|
||||
"block_production_chains": ["ait-mainnet"],
|
||||
"enable_block_production": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"_meta": {
|
||||
"generated_at": "2026-05-19T15:50:00Z",
|
||||
"format_version": "1.0",
|
||||
"total_islands": 1,
|
||||
"active_islands": 1,
|
||||
"note": "This endpoint only shows islands where this node (aitbc) is the HUB"
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
{
|
||||
"chain_id": "ait-mainnet",
|
||||
"island_id": "ait-mainnet-island",
|
||||
"name": "AIT Mainnet",
|
||||
"description": "Primary production chain for AITBC platform",
|
||||
"type": "production",
|
||||
"status": "active",
|
||||
"requirements": {
|
||||
"minimum_version": "1.0.0",
|
||||
"required_services": ["aitbc-blockchain-node", "aitbc-blockchain-rpc"],
|
||||
"ports": {
|
||||
"p2p": 7070,
|
||||
"rpc": 8006
|
||||
}
|
||||
},
|
||||
"how_to_join": {
|
||||
"step_1_environment": {
|
||||
"description": "Configure node.env for this node",
|
||||
"file": "/etc/aitbc/node.env",
|
||||
"variables": {
|
||||
"NODE_ID": "your-node-id",
|
||||
"p2p_node_id": "node-$(uuidgen | tr -d '-')",
|
||||
"p2p_bind_host": "0.0.0.0",
|
||||
"p2p_bind_port": "7070",
|
||||
"proposer_id": "your-proposer-id",
|
||||
"p2p_peers": "aitbc:7070,aitbc1:7070",
|
||||
"enable_block_production": "false",
|
||||
"block_production_chains": ""
|
||||
},
|
||||
"note": "Set enable_block_production=false for follower nodes"
|
||||
},
|
||||
"step_2_blockchain_env": {
|
||||
"description": "Configure blockchain.env",
|
||||
"file": "/etc/aitbc/blockchain.env",
|
||||
"variables": {
|
||||
"island_id": "ait-mainnet-island",
|
||||
"supported_chains": "ait-mainnet",
|
||||
"CHAIN_ID": "ait-mainnet",
|
||||
"auto_sync_enabled": "true",
|
||||
"default_peer_rpc_url": "http://aitbc1:8006",
|
||||
"SYNC_SOURCE_HOST": "aitbc1",
|
||||
"SYNC_SOURCE_PORT": "8006",
|
||||
"SYNC_CHAIN_ID": "ait-mainnet"
|
||||
}
|
||||
},
|
||||
"step_3_networking": {
|
||||
"description": "Ensure network connectivity",
|
||||
"requirements": [
|
||||
"Port 7070 open for P2P communication",
|
||||
"Port 8006 open for RPC access",
|
||||
"Connectivity to aitbc (10.1.223.93) and aitbc1 (10.1.223.40)"
|
||||
]
|
||||
},
|
||||
"step_4_start_services": {
|
||||
"description": "Start blockchain services",
|
||||
"commands": [
|
||||
"systemctl start aitbc-blockchain-node.service",
|
||||
"systemctl start aitbc-blockchain-rpc.service"
|
||||
]
|
||||
},
|
||||
"step_5_verify": {
|
||||
"description": "Verify successful join",
|
||||
"commands": [
|
||||
"curl -s http://localhost:8006/rpc/head",
|
||||
"sqlite3 /var/lib/aitbc/data/ait-mainnet/chain.db 'SELECT MAX(height) FROM block'"
|
||||
],
|
||||
"expected_result": "Block height should be increasing as blocks are synced"
|
||||
}
|
||||
},
|
||||
"endpoints": {
|
||||
"rpc": {
|
||||
"hub": "http://aitbc:8006",
|
||||
"backup": "http://aitbc1:8006"
|
||||
},
|
||||
"p2p": {
|
||||
"peers": ["aitbc:7070", "aitbc1:7070"]
|
||||
}
|
||||
},
|
||||
"configuration_templates": {
|
||||
"follower_node": {
|
||||
"node.env": {
|
||||
"NODE_ID": "your-node-id",
|
||||
"p2p_node_id": "node-unique-uuid",
|
||||
"p2p_bind_host": "0.0.0.0",
|
||||
"p2p_bind_port": "7070",
|
||||
"proposer_id": "ait1your-proposer-id",
|
||||
"p2p_peers": "aitbc:7070,aitbc1:7070",
|
||||
"trusted_proposers": "",
|
||||
"block_production_chains": "",
|
||||
"enable_block_production": "false"
|
||||
},
|
||||
"blockchain.env": {
|
||||
"auto_sync_enabled": "true",
|
||||
"island_id": "ait-mainnet-island",
|
||||
"supported_chains": "ait-mainnet",
|
||||
"default_peer_rpc_url": "http://aitbc1:8006",
|
||||
"SYNC_SOURCE_HOST": "aitbc1",
|
||||
"SYNC_SOURCE_PORT": "8006",
|
||||
"SYNC_CHAIN_ID": "ait-mainnet"
|
||||
}
|
||||
}
|
||||
},
|
||||
"troubleshooting": {
|
||||
"gap_detected_errors": {
|
||||
"cause": "Node is receiving blocks but missing intermediate blocks",
|
||||
"solution": "Ensure auto_sync_enabled=true and default_peer_rpc_url points to active hub"
|
||||
},
|
||||
"fork_detection": {
|
||||
"cause": "Multiple nodes with same proposer_id producing blocks",
|
||||
"solution": "Ensure enable_block_production=false on follower nodes"
|
||||
},
|
||||
"p2p_connection_failed": {
|
||||
"cause": "Cannot connect to P2P peers",
|
||||
"solution": "Verify p2p_bind_host and p2p_bind_port are set correctly, check firewall rules"
|
||||
}
|
||||
},
|
||||
"_meta": {
|
||||
"format_version": "1.0",
|
||||
"updated_at": "2026-05-19T15:50:00Z"
|
||||
}
|
||||
}
|
||||
317
website/agent/live_api.py
Normal file
317
website/agent/live_api.py
Normal file
@@ -0,0 +1,317 @@
|
||||
#!/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)
|
||||
109
website/nginx-example.conf
Normal file
109
website/nginx-example.conf
Normal file
@@ -0,0 +1,109 @@
|
||||
# AITBC Agent-First Website - Nginx Configuration Example (Live-Only)
|
||||
#
|
||||
# This configuration serves ONLY live data from the blockchain RPC.
|
||||
# No static JSON files - all endpoints query the node in real-time.
|
||||
#
|
||||
# Requirements:
|
||||
# - live_api.py running on port 8080
|
||||
# - aitbc-blockchain-rpc service running on port 8006
|
||||
#
|
||||
# Installation:
|
||||
# sudo cp nginx-example.conf /etc/nginx/sites-available/aitbc-agent
|
||||
# sudo ln -s /etc/nginx/sites-available/aitbc-agent /etc/nginx/sites-enabled/
|
||||
# sudo nginx -t
|
||||
# sudo systemctl reload nginx
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
server_name aitbc.bubuit.net localhost;
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
||||
|
||||
# ============================================
|
||||
# AGENT API - Live-only (real-time from RPC)
|
||||
# ============================================
|
||||
# All agent endpoints proxy to live_api.py service on port 8081
|
||||
# No static files - everything is queried live from blockchain
|
||||
|
||||
location /agent/health {
|
||||
proxy_pass http://127.0.0.1:8081/agent/health;
|
||||
proxy_set_header Host $host;
|
||||
add_header Access-Control-Allow-Origin * always;
|
||||
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
|
||||
}
|
||||
|
||||
location /agent/discovery.json {
|
||||
proxy_pass http://127.0.0.1:8081/agent/discovery.json;
|
||||
proxy_set_header Host $host;
|
||||
add_header Access-Control-Allow-Origin * always;
|
||||
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
|
||||
}
|
||||
|
||||
location /agent/islands.json {
|
||||
proxy_pass http://127.0.0.1:8081/agent/islands.json;
|
||||
proxy_set_header Host $host;
|
||||
add_header Access-Control-Allow-Origin * always;
|
||||
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
|
||||
}
|
||||
|
||||
location /agent/chains.json {
|
||||
proxy_pass http://127.0.0.1:8081/agent/chains.json;
|
||||
proxy_set_header Host $host;
|
||||
add_header Access-Control-Allow-Origin * always;
|
||||
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
|
||||
}
|
||||
|
||||
# Agent landing page (static HTML for human readability)
|
||||
location /agent/ {
|
||||
alias /opt/aitbc/website/agent/;
|
||||
index index.html;
|
||||
try_files $uri $uri/ =404;
|
||||
|
||||
# CORS headers
|
||||
add_header Access-Control-Allow-Origin * always;
|
||||
add_header Access-Control-Allow-Methods "GET, OPTIONS" always;
|
||||
add_header Access-Control-Allow-Headers "Content-Type, Accept" always;
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# RPC ENDPOINTS - Direct blockchain access
|
||||
# ============================================
|
||||
location /rpc/ {
|
||||
proxy_pass http://127.0.0.1:8006/rpc/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
add_header Access-Control-Allow-Origin * always;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
|
||||
|
||||
if ($request_method = 'OPTIONS') {
|
||||
return 204;
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# MAIN WEBSITE - Agent discovery interface
|
||||
# ============================================
|
||||
location / {
|
||||
root /opt/aitbc/website;
|
||||
index index.html;
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
# Static assets caching
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# Logging
|
||||
access_log /var/log/nginx/aitbc-agent.access.log;
|
||||
error_log /var/log/nginx/aitbc-agent.error.log;
|
||||
}
|
||||
32
website/systemd-example.service
Normal file
32
website/systemd-example.service
Normal file
@@ -0,0 +1,32 @@
|
||||
[Unit]
|
||||
Description=AITBC Agent Live API
|
||||
Documentation=https://github.com/oib/AITBC
|
||||
After=aitbc-blockchain-rpc.service
|
||||
Wants=aitbc-blockchain-rpc.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=/opt/aitbc/website/agent
|
||||
|
||||
# Environment variables - customize for your node
|
||||
Environment="AITBC_RPC_URL=http://localhost:8006/rpc"
|
||||
Environment="NODE_ID=aitbc"
|
||||
Environment="ISLAND_ID=ait-mainnet-island"
|
||||
Environment="CHAIN_ID=ait-mainnet"
|
||||
Environment="NODE_ROLE=hub"
|
||||
Environment="PYTHONUNBUFFERED=1"
|
||||
|
||||
# For testnet hub (aitbc1), use these instead:
|
||||
# Environment="NODE_ID=aitbc1"
|
||||
# Environment="ISLAND_ID=ait-testnet-island"
|
||||
# Environment="CHAIN_ID=ait-testnet"
|
||||
|
||||
# Python path - update if your venv is elsewhere
|
||||
ExecStart=/opt/aitbc/venv/bin/python /opt/aitbc/website/agent/live_api.py --host 127.0.0.1 --port 8081
|
||||
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in New Issue
Block a user