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>
|
</header>
|
||||||
|
|
||||||
<div class="note">
|
<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>
|
</div>
|
||||||
|
|
||||||
<section class="section">
|
<section class="section">
|
||||||
@@ -176,12 +176,7 @@
|
|||||||
<p><span class="status-indicator"></span>active</p>
|
<p><span class="status-indicator"></span>active</p>
|
||||||
<p class="description">Island: ait-mainnet-island</p>
|
<p class="description">Island: ait-mainnet-island</p>
|
||||||
<p class="description">Role: Hub (Block Production)</p>
|
<p class="description">Role: Hub (Block Production)</p>
|
||||||
</div>
|
<p class="description" style="font-size: 0.75rem; color: var(--warning);">Live data from RPC</p>
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</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