diff --git a/README.md b/README.md index 188ed764..58387cce 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,49 @@ aitbc --help --language german aitbc marketplace list --translate-to french ``` +## 🔗 Blockchain Node (Brother Chain) + +A minimal asset-backed blockchain that validates compute receipts and mints AIT tokens. + +### ✅ Current Status +- **Chain ID**: `ait-devnet` +- **Consensus**: Proof-of-Authority (single proposer) +- **RPC Endpoint**: `http://localhost:8026/rpc` +- **Health Check**: `http://localhost:8026/health` +- **Metrics**: `http://localhost:8026/metrics` (Prometheus format) +- **Status**: 🟢 Operational and fully functional + +### 🚀 Quick Launch + +```bash +cd /opt/aitbc/apps/blockchain-node +source .venv/bin/activate +bash scripts/devnet_up.sh +``` + +The node starts: +- Proposer loop (block production) +- RPC API on port 8026 +- Mock coordinator on port 8090 (for testing) + +### 🛠️ CLI Interaction + +```bash +# Check node status +aitbc blockchain status + +# Get chain head +aitbc blockchain head + +# Check balance +aitbc blockchain balance --address + +# Fund an address (devnet faucet) +aitbc blockchain faucet --address --amount 1000 +``` + +For full documentation, see: [`apps/blockchain-node/README.md`](./apps/blockchain-node/README.md) + ## 🤖 Agent-First Computing AITBC creates an ecosystem where AI agents are the primary participants: diff --git a/apps/blockchain-node/README.md b/apps/blockchain-node/README.md index fac8e043..4bb163c3 100644 --- a/apps/blockchain-node/README.md +++ b/apps/blockchain-node/README.md @@ -1,25 +1,169 @@ -# Blockchain Node +# Blockchain Node (Brother Chain) -## Purpose & Scope - -Minimal asset-backed blockchain node that validates compute receipts and mints AIT tokens as described in `docs/bootstrap/blockchain_node.md`. +Minimal asset-backed blockchain node that validates compute receipts and mints AIT tokens. ## Status -Scaffolded. Implementation pending per staged roadmap. +✅ **Operational** — Core blockchain functionality implemented and running. -## Devnet Tooling +### Capabilities +- PoA consensus with single proposer (devnet) +- Transaction processing (TRANSFER, RECEIPT_CLAIM) +- Receipt validation and minting +- Gossip-based peer-to-peer networking (in-memory backend) +- RESTful RPC API (`/rpc/*`) +- Prometheus metrics (`/metrics`) +- Health check endpoint (`/health`) +- SQLite persistence with Alembic migrations -- `scripts/make_genesis.py` — Generate a deterministic devnet genesis file (`data/devnet/genesis.json`). -- `scripts/keygen.py` — Produce throwaway devnet keypairs (printed or written to disk). -- `scripts/devnet_up.sh` — Launch the blockchain node and RPC API with a freshly generated genesis file. +## Quickstart (Devnet) -### Quickstart +The blockchain node is already set up with a virtualenv. To launch: ```bash -cd apps/blockchain-node -python scripts/make_genesis.py --force +cd /opt/aitbc/apps/blockchain-node +source .venv/bin/activate bash scripts/devnet_up.sh ``` -The script sets `PYTHONPATH=src` and starts the proposer loop plus the FastAPI app (via `uvicorn`). Press `Ctrl+C` to stop the devnet. +This will: +1. Generate genesis block at `data/devnet/genesis.json` +2. Start the blockchain node proposer loop (PID logged) +3. Start RPC API on `http://127.0.0.1:8026` +4. Start mock coordinator on `http://127.0.0.1:8090` + +Press `Ctrl+C` to stop all processes. + +### Manual Startup + +If you prefer to start components separately: + +```bash +# Terminal 1: Blockchain node +cd /opt/aitbc/apps/blockchain-node +source .venv/bin/activate +PYTHONPATH=src python -m aitbc_chain.main + +# Terminal 2: RPC API +cd /opt/aitbc/apps/blockchain-node +source .venv/bin/activate +PYTHONPATH=src uvicorn aitbc_chain.app:app --host 127.0.0.1 --port 8026 + +# Terminal 3: Mock coordinator (optional, for testing) +cd /opt/aitbc/apps/blockchain-node +source .venv/bin/activate +PYTHONPATH=src uvicorn mock_coordinator:app --host 127.0.0.1 --port 8090 +``` + +## API Endpoints + +Once running, the RPC API is available at `http://127.0.0.1:8026/rpc`. + +### Health & Metrics +- `GET /health` — Health check with node info +- `GET /metrics` — Prometheus-format metrics + +### Blockchain Queries +- `GET /rpc/head` — Current chain head block +- `GET /rpc/blocks/{height}` — Get block by height +- `GET /rpc/blocks-range?start=0&end=10` — Get block range +- `GET /rpc/info` — Chain information +- `GET /rpc/supply` — Token supply info +- `GET /rpc/validators` — List validators +- `GET /rpc/state` — Full state dump + +### Transactions +- `POST /rpc/sendTx` — Submit transaction (JSON body: `TransactionRequest`) +- `GET /rpc/transactions` — Latest transactions +- `GET /rpc/tx/{tx_hash}` — Get transaction by hash +- `POST /rpc/estimateFee` — Estimate fee for transaction type + +### Receipts (Compute Proofs) +- `POST /rpc/submitReceipt` — Submit receipt claim +- `GET /rpc/receipts` — Latest receipts +- `GET /rpc/receipts/{receipt_id}` — Get receipt by ID + +### Accounts +- `GET /rpc/getBalance/{address}` — Account balance +- `GET /rpc/address/{address}` — Address details + txs +- `GET /rpc/addresses` — List active addresses + +### Admin +- `POST /rpc/admin/mintFaucet` — Mint devnet funds (requires admin key) + +### Sync +- `GET /rpc/syncStatus` — Chain sync status + +## CLI Integration + +Use the AITBC CLI to interact with the node: + +```bash +source /opt/aitbc/cli/venv/bin/activate +aitbc blockchain status +aitbc blockchain head +aitbc blockchain balance --address +aitbc blockchain faucet --address --amount 1000 +``` + +## Configuration + +Edit `.env` in this directory to change: + +``` +CHAIN_ID=ait-devnet +DB_PATH=./data/chain.db +RPC_BIND_HOST=0.0.0.0 +RPC_BIND_PORT=8026 +P2P_BIND_HOST=0.0.0.0 +P2P_BIND_PORT=7070 +PROPOSER_KEY=proposer_key_ +MINT_PER_UNIT=1000 +COORDINATOR_RATIO=0.05 +GOSSIP_BACKEND=memory +``` + +Restart the node after changes. + +## Project Layout + +``` +blockchain-node/ +├── src/aitbc_chain/ +│ ├── app.py # FastAPI app + routes +│ ├── main.py # Proposer loop + startup +│ ├── config.py # Settings from .env +│ ├── database.py # DB init + session mgmt +│ ├── mempool.py # Transaction mempool +│ ├── gossip/ # P2P message bus +│ ├── consensus/ # PoA proposer logic +│ ├── rpc/ # RPC endpoints +│ ├── contracts/ # Smart contract logic +│ └── models.py # SQLModel definitions +├── data/ +│ └── devnet/ +│ └── genesis.json # Generated by make_genesis.py +├── scripts/ +│ ├── make_genesis.py # Genesis generator +│ ├── devnet_up.sh # Devnet launcher +│ └── keygen.py # Keypair generator +└── .env # Node configuration +``` + +## Notes + +- The node uses proof-of-authority (PoA) consensus with a single proposer for the devnet. +- Transactions require a valid signature (ed25519) unless running in test mode. +- Receipts represent compute work attestations and mint new AIT tokens to the miner. +- Gossip backend defaults to in-memory; for multi-node networks, configure a Redis backend. +- RPC API does not require authentication on devnet (add in production). + +## Troubleshooting + +**Port already in use:** Change `RPC_BIND_PORT` in `.env` and restart. + +**Database locked:** Ensure only one node instance is running; delete `data/chain.db` if corrupted. + +**No blocks proposed:** Check proposer logs; ensure `PROPOSER_KEY` is set and no other proposers are conflicting. + +**Mock coordinator not responding:** It's only needed for certain tests; the blockchain node can run standalone. diff --git a/apps/blockchain-node/data/devnet/genesis.json b/apps/blockchain-node/data/devnet/genesis.json deleted file mode 100644 index 7bccd0d1..00000000 --- a/apps/blockchain-node/data/devnet/genesis.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "accounts": [ - { - "address": "ait1faucet000000000000000000000000000000000", - "balance": 1000000000, - "nonce": 0 - } - ], - "authorities": [ - { - "address": "ait1devproposer000000000000000000000000000000", - "weight": 1 - } - ], - "chain_id": "ait-devnet", - "params": { - "base_fee": 10, - "coordinator_ratio": 0.05, - "fee_per_byte": 1, - "mint_per_unit": 1000 - }, - "timestamp": 1772895053 -} diff --git a/cli/aitbc_cli/commands/advanced_analytics.py b/cli/aitbc_cli/commands/advanced_analytics.py index fd330992..9e8d8fd9 100755 --- a/cli/aitbc_cli/commands/advanced_analytics.py +++ b/cli/aitbc_cli/commands/advanced_analytics.py @@ -10,29 +10,15 @@ import json from typing import Optional, List, Dict, Any from datetime import datetime, timedelta -# Import advanced analytics with robust path resolution +# Ensure coordinator-api src is on path for app.services imports import os import sys - -_services_path = os.environ.get('AITBC_SERVICES_PATH') -if _services_path: - if os.path.isdir(_services_path): - if _services_path not in sys.path: - sys.path.insert(0, _services_path) - else: - print(f"Warning: AITBC_SERVICES_PATH set but not a directory: {_services_path}", file=sys.stderr) -else: - _project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')) - _computed_services = os.path.join(_project_root, 'apps', 'coordinator-api', 'src', 'app', 'services') - if os.path.isdir(_computed_services) and _computed_services not in sys.path: - sys.path.insert(0, _computed_services) - else: - _fallback = '/home/oib/windsurf/aitbc/apps/coordinator-api/src/app/services' - if os.path.isdir(_fallback) and _fallback not in sys.path: - sys.path.insert(0, _fallback) +_src_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'apps', 'coordinator-api', 'src')) +if _src_path not in sys.path: + sys.path.insert(0, _src_path) try: - from advanced_analytics import ( + from app.services.advanced_analytics import ( start_analytics_monitoring, stop_analytics_monitoring, get_dashboard_data, create_analytics_alert, get_analytics_summary, advanced_analytics, MetricType, Timeframe @@ -43,8 +29,8 @@ except ImportError as e: def _missing(*args, **kwargs): raise ImportError( - f"Required service module 'advanced_analytics' could not be imported: {_import_error}. " - "Ensure coordinator-api dependencies are installed or set AITBC_SERVICES_PATH." + f"Required service module 'app.services.advanced_analytics' could not be imported: {_import_error}. " + "Ensure coordinator-api dependencies are installed and the source directory is accessible." ) start_analytics_monitoring = stop_analytics_monitoring = get_dashboard_data = _missing create_analytics_alert = get_analytics_summary = _missing diff --git a/cli/aitbc_cli/commands/ai_surveillance.py b/cli/aitbc_cli/commands/ai_surveillance.py index 0ddca999..6dbc1b8a 100755 --- a/cli/aitbc_cli/commands/ai_surveillance.py +++ b/cli/aitbc_cli/commands/ai_surveillance.py @@ -10,29 +10,15 @@ import json from typing import Optional, List, Dict, Any from datetime import datetime -# Import AI surveillance system with robust path resolution +# Ensure coordinator-api src is on path for app.services imports import os import sys - -_services_path = os.environ.get('AITBC_SERVICES_PATH') -if _services_path: - if os.path.isdir(_services_path): - if _services_path not in sys.path: - sys.path.insert(0, _services_path) - else: - print(f"Warning: AITBC_SERVICES_PATH set but not a directory: {_services_path}", file=sys.stderr) -else: - _project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')) - _computed_services = os.path.join(_project_root, 'apps', 'coordinator-api', 'src', 'app', 'services') - if os.path.isdir(_computed_services) and _computed_services not in sys.path: - sys.path.insert(0, _computed_services) - else: - _fallback = '/home/oib/windsurf/aitbc/apps/coordinator-api/src/app/services' - if os.path.isdir(_fallback) and _fallback not in sys.path: - sys.path.insert(0, _fallback) +_src_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'apps', 'coordinator-api', 'src')) +if _src_path not in sys.path: + sys.path.insert(0, _src_path) try: - from ai_surveillance import ( + from app.services.ai_surveillance import ( start_ai_surveillance, stop_ai_surveillance, get_surveillance_summary, get_user_risk_profile, list_active_alerts, analyze_behavior_patterns, ai_surveillance, SurveillanceType, RiskLevel, AlertPriority @@ -43,8 +29,8 @@ except ImportError as e: def _missing(*args, **kwargs): raise ImportError( - f"Required service module 'ai_surveillance' could not be imported: {_import_error}. " - "Ensure coordinator-api dependencies are installed or set AITBC_SERVICES_PATH." + f"Required service module 'app.services.ai_surveillance' could not be imported: {_import_error}. " + "Ensure coordinator-api dependencies are installed and the source directory is accessible." ) start_ai_surveillance = stop_ai_surveillance = get_surveillance_summary = _missing get_user_risk_profile = list_active_alerts = analyze_behavior_patterns = _missing diff --git a/cli/aitbc_cli/commands/ai_trading.py b/cli/aitbc_cli/commands/ai_trading.py index a145ad8d..65979357 100755 --- a/cli/aitbc_cli/commands/ai_trading.py +++ b/cli/aitbc_cli/commands/ai_trading.py @@ -10,29 +10,15 @@ import json from typing import Optional, List, Dict, Any from datetime import datetime, timedelta -# Import AI trading engine with robust path resolution +# Ensure coordinator-api src is on path for app.services imports import os import sys - -_services_path = os.environ.get('AITBC_SERVICES_PATH') -if _services_path: - if os.path.isdir(_services_path): - if _services_path not in sys.path: - sys.path.insert(0, _services_path) - else: - print(f"Warning: AITBC_SERVICES_PATH set but not a directory: {_services_path}", file=sys.stderr) -else: - _project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')) - _computed_services = os.path.join(_project_root, 'apps', 'coordinator-api', 'src', 'app', 'services') - if os.path.isdir(_computed_services) and _computed_services not in sys.path: - sys.path.insert(0, _computed_services) - else: - _fallback = '/home/oib/windsurf/aitbc/apps/coordinator-api/src/app/services' - if os.path.isdir(_fallback) and _fallback not in sys.path: - sys.path.insert(0, _fallback) +_src_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'apps', 'coordinator-api', 'src')) +if _src_path not in sys.path: + sys.path.insert(0, _src_path) try: - from ai_trading_engine import ( + from app.services.ai_trading_engine import ( initialize_ai_engine, train_strategies, generate_trading_signals, get_engine_status, ai_trading_engine, TradingStrategy ) @@ -42,8 +28,8 @@ except ImportError as e: def _missing(*args, **kwargs): raise ImportError( - f"Required service module 'ai_trading_engine' could not be imported: {_import_error}. " - "Ensure coordinator-api dependencies are installed or set AITBC_SERVICES_PATH." + f"Required service module 'app.services.ai_trading_engine' could not be imported: {_import_error}. " + "Ensure coordinator-api dependencies are installed and the source directory is accessible." ) initialize_ai_engine = train_strategies = generate_trading_signals = get_engine_status = _missing ai_trading_engine = None diff --git a/cli/aitbc_cli/commands/enterprise_integration.py b/cli/aitbc_cli/commands/enterprise_integration.py index 79a56c0b..f68f3c6f 100755 --- a/cli/aitbc_cli/commands/enterprise_integration.py +++ b/cli/aitbc_cli/commands/enterprise_integration.py @@ -10,41 +10,32 @@ import json from typing import Optional, List, Dict, Any from datetime import datetime -# Import enterprise integration services using importlib to avoid naming conflicts -import importlib.util +# Ensure coordinator-api src is on path for app.services imports import os +import sys +_src_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'apps', 'coordinator-api', 'src')) +if _src_path not in sys.path: + sys.path.insert(0, _src_path) -_services_path = os.environ.get('AITBC_SERVICES_PATH') -if _services_path: - base_dir = _services_path -else: - _project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')) - base_dir = os.path.join(_project_root, 'apps', 'coordinator-api', 'src', 'app', 'services') - if not os.path.isdir(base_dir): - base_dir = '/home/oib/windsurf/aitbc/apps/coordinator-api/src/app/services' +try: + from app.services.enterprise_integration import ( + create_tenant, get_tenant_info, generate_api_key, + register_integration, get_system_status, list_tenants, + list_integrations + ) + # Get EnterpriseAPIGateway if available + import app.services.enterprise_integration as ei_module + EnterpriseAPIGateway = getattr(ei_module, 'EnterpriseAPIGateway', None) + _import_error = None +except ImportError as e: + _import_error = e -module_path = os.path.join(base_dir, 'enterprise_integration.py') -if os.path.isfile(module_path): - spec = importlib.util.spec_from_file_location("enterprise_integration_service", module_path) - ei = importlib.util.module_from_spec(spec) - spec.loader.exec_module(ei) - create_tenant = ei.create_tenant - get_tenant_info = ei.get_tenant_info - generate_api_key = ei.generate_api_key - register_integration = ei.register_integration - get_system_status = ei.get_system_status - list_tenants = ei.list_tenants - list_integrations = ei.list_integrations - EnterpriseAPIGateway = getattr(ei, 'EnterpriseAPIGateway', None) -else: - # Provide stubs if module not found def _missing(*args, **kwargs): raise ImportError( - f"Could not load enterprise_integration.py from {module_path}. " - "Ensure coordinator-api services are available or set AITBC_SERVICES_PATH." + f"Required service module 'app.services.enterprise_integration' could not be imported: {_import_error}. " + "Ensure coordinator-api dependencies are installed and the source directory is accessible." ) - create_tenant = get_tenant_info = generate_api_key = _missing - register_integration = get_system_status = list_tenants = list_integrations = _missing + create_tenant = get_tenant_info = generate_api_key = register_integration = get_system_status = list_tenants = list_integrations = _missing EnterpriseAPIGateway = None @click.group() diff --git a/cli/aitbc_cli/commands/regulatory.py b/cli/aitbc_cli/commands/regulatory.py index 0f19de58..9c520af8 100755 --- a/cli/aitbc_cli/commands/regulatory.py +++ b/cli/aitbc_cli/commands/regulatory.py @@ -10,29 +10,15 @@ import json from typing import Optional, List, Dict, Any from datetime import datetime, timedelta -# Import regulatory reporting system with robust path resolution +# Ensure coordinator-api src is on path for app.services imports import os import sys - -_services_path = os.environ.get('AITBC_SERVICES_PATH') -if _services_path: - if os.path.isdir(_services_path): - if _services_path not in sys.path: - sys.path.insert(0, _services_path) - else: - print(f"Warning: AITBC_SERVICES_PATH set but not a directory: {_services_path}", file=sys.stderr) -else: - _project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')) - _computed_services = os.path.join(_project_root, 'apps', 'coordinator-api', 'src', 'app', 'services') - if os.path.isdir(_computed_services) and _computed_services not in sys.path: - sys.path.insert(0, _computed_services) - else: - _fallback = '/home/oib/windsurf/aitbc/apps/coordinator-api/src/app/services' - if os.path.isdir(_fallback) and _fallback not in sys.path: - sys.path.insert(0, _fallback) +_src_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'apps', 'coordinator-api', 'src')) +if _src_path not in sys.path: + sys.path.insert(0, _src_path) try: - from regulatory_reporting import ( + from app.services.regulatory_reporting import ( generate_sar, generate_compliance_summary, list_reports, regulatory_reporter, ReportType, ReportStatus, RegulatoryBody ) @@ -42,8 +28,8 @@ except ImportError as e: def _missing(*args, **kwargs): raise ImportError( - f"Required service module 'regulatory_reporting' could not be imported: {_import_error}. " - "Ensure coordinator-api dependencies are installed or set AITBC_SERVICES_PATH." + f"Required service module 'app.services.regulatory_reporting' could not be imported: {_import_error}. " + "Ensure coordinator-api dependencies are installed and the source directory is accessible." ) generate_sar = generate_compliance_summary = list_reports = regulatory_reporter = _missing diff --git a/cli/aitbc_cli/commands/surveillance.py b/cli/aitbc_cli/commands/surveillance.py index aff43994..496709d0 100755 --- a/cli/aitbc_cli/commands/surveillance.py +++ b/cli/aitbc_cli/commands/surveillance.py @@ -10,33 +10,16 @@ import json from typing import Optional, List, Dict, Any from datetime import datetime, timedelta -# Import surveillance system with robust path resolution +# Ensure coordinator-api src is on path for app.services imports import os import sys - -# Determine services path: use AITBC_SERVICES_PATH if set, else compute relative to repo layout -_services_path = os.environ.get('AITBC_SERVICES_PATH') -if _services_path: - if os.path.isdir(_services_path): - if _services_path not in sys.path: - sys.path.insert(0, _services_path) - else: - print(f"Warning: AITBC_SERVICES_PATH set but not a directory: {_services_path}", file=sys.stderr) -else: - # Compute project root relative to this file: cli/aitbc_cli/commands -> 3 levels up to project root - _project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')) - _computed_services = os.path.join(_project_root, 'apps', 'coordinator-api', 'src', 'app', 'services') - if os.path.isdir(_computed_services) and _computed_services not in sys.path: - sys.path.insert(0, _computed_services) - else: - # Fallback to known hardcoded path if it exists (for legacy deployments) - _fallback = '/home/oib/windsurf/aitbc/apps/coordinator-api/src/app/services' - if os.path.isdir(_fallback) and _fallback not in sys.path: - sys.path.insert(0, _fallback) +_src_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'apps', 'coordinator-api', 'src')) +if _src_path not in sys.path: + sys.path.insert(0, _src_path) try: - from trading_surveillance import ( - start_surveillance, stop_surveillance, get_alerts, + from app.services.trading_surveillance import ( + start_surveillance, stop_surveillance, get_alerts, get_surveillance_summary, AlertLevel ) _import_error = None @@ -45,8 +28,8 @@ except ImportError as e: def _missing(*args, **kwargs): raise ImportError( - f"Required service module 'trading_surveillance' could not be imported: {_import_error}. " - "Ensure coordinator-api dependencies are installed or set AITBC_SERVICES_PATH." + f"Required service module 'app.services.trading_surveillance' could not be imported: {_import_error}. " + "Ensure coordinator-api dependencies are installed and the source directory is accessible." ) start_surveillance = stop_surveillance = get_alerts = get_surveillance_summary = _missing @@ -237,7 +220,7 @@ def resolve(ctx, alert_id: str, resolution: str): click.echo(f"🔍 Resolving alert: {alert_id}") # Import surveillance to access resolve function - from trading_surveillance import surveillance + from app.services.trading_surveillance import surveillance success = surveillance.resolve_alert(alert_id, resolution) @@ -263,7 +246,7 @@ def test(ctx, symbols: str, duration: int): click.echo(f"⏱️ Duration: {duration} seconds") # Import test function - from trading_surveillance import test_trading_surveillance + from app.services.trading_surveillance import test_trading_surveillance # Run test asyncio.run(test_trading_surveillance()) @@ -289,7 +272,7 @@ def test(ctx, symbols: str, duration: int): def status(ctx): """Show current surveillance status""" try: - from trading_surveillance import surveillance + from app.services.trading_surveillance import surveillance click.echo(f"📊 Trading Surveillance Status")