Files
aitbc/apps/blockchain-node/src/aitbc_chain/app.py
aitbc 852f2e5a8a
Some checks failed
Cross-Node Transaction Testing / transaction-test (push) Has been cancelled
Deploy to Testnet / deploy-testnet (push) Has been cancelled
Documentation Validation / validate-docs (push) Has been cancelled
Documentation Validation / validate-policies-strict (push) Has been cancelled
Multi-Node Stress Testing / stress-test (push) Has been cancelled
Node Failover Simulation / failover-test (push) Has been cancelled
Integration Tests / test-service-integration (push) Has been cancelled
Security Scanning / security-scan (push) Has been cancelled
Python Tests / test-python (push) Has been cancelled
CLI Tests / test-cli (push) Has been cancelled
Blockchain Synchronization Verification / sync-verification (push) Successful in 11s
Contract Performance Benchmarks / benchmark-gas-usage (push) Successful in 1m36s
Contract Performance Benchmarks / benchmark-execution-time (push) Successful in 1m24s
Contract Performance Benchmarks / benchmark-throughput (push) Successful in 1m25s
Cross-Chain Functionality Tests / test-cross-chain-sync (push) Successful in 2s
Cross-Chain Functionality Tests / test-cross-chain-transactions (push) Successful in 5s
Cross-Chain Functionality Tests / test-cross-chain-bridge (push) Has been skipped
Cross-Chain Functionality Tests / test-multi-chain-consensus (push) Successful in 3s
Cross-Chain Functionality Tests / aggregate-results (push) Has been skipped
Multi-Chain Island Architecture Tests / test-multi-chain-island (push) Successful in 2s
Multi-Node Blockchain Health Monitoring / health-check (push) Successful in 3s
P2P Network Verification / p2p-verification (push) Successful in 2s
Smart Contract Tests / test-solidity (map[name:aitbc-contracts path:contracts]) (push) Failing after 1m28s
Smart Contract Tests / test-solidity (map[name:aitbc-token path:packages/solidity/aitbc-token]) (push) Successful in 21s
Smart Contract Tests / test-foundry (push) Failing after 20s
Smart Contract Tests / lint-solidity (push) Successful in 30s
Smart Contract Tests / deploy-contracts (push) Successful in 1m40s
Systemd Sync / sync-systemd (push) Successful in 26s
Contract Performance Benchmarks / compare-benchmarks (push) Successful in 4s
Rename openclaw to hermes across documentation and workflows
- Update workflow paths from docs/openclaw to docs/hermes
- Rename skill prefixes from openclaw-* to hermes-*
- Update agent skill references in refactoring and analysis docs
- Rename OPENCLAW_AITBC_MASTERY_PLAN.md to reflect hermes branding
- Update CLI examples and command references throughout documentation
2026-05-07 11:42:06 +02:00

224 lines
8.8 KiB
Python
Executable File

from __future__ import annotations
import asyncio
import os
import time
from collections import defaultdict
from contextlib import asynccontextmanager
from fastapi import APIRouter, FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse, PlainTextResponse
from starlette.middleware.base import BaseHTTPMiddleware
from .config import settings
from .database import init_db, session_scope
from .gossip import create_backend, gossip_broker
from .logger import get_logger
from .mempool import init_mempool
from .metrics import metrics_registry
from .rpc.router import router as rpc_router, set_poa_proposer
from .rpc.websocket import router as websocket_router
# from .escrow_routes import router as escrow_router # Not yet implemented
_app_logger = get_logger("aitbc_chain.app")
def _env_value(*names: str) -> str | None:
for name in names:
value = os.getenv(name)
if value is not None:
return value
return None
class RateLimitMiddleware(BaseHTTPMiddleware):
"""Simple in-memory rate limiter per client IP."""
def __init__(self, app, max_requests: int = 100, window_seconds: int = 60):
super().__init__(app)
self._max_requests = max_requests
self._window = window_seconds
self._requests: dict[str, list[float]] = defaultdict(list)
async def dispatch(self, request: Request, call_next):
client_ip = request.client.host if request.client else "unknown"
# Bypass rate limiting for localhost and internal network (sync/health internal traffic)
trusted_ips = os.getenv("AITBC_TRUSTED_IPS", "127.0.0.1,::1").split(",")
if client_ip in trusted_ips:
return await call_next(request)
now = time.time()
# Clean old entries
self._requests[client_ip] = [
t for t in self._requests[client_ip] if now - t < self._window
]
if len(self._requests[client_ip]) >= self._max_requests:
metrics_registry.increment("rpc_rate_limited_total")
return JSONResponse(
status_code=429,
content={"detail": "Rate limit exceeded"},
headers={"Retry-After": str(self._window)},
)
self._requests[client_ip].append(now)
return await call_next(request)
class RequestLoggingMiddleware(BaseHTTPMiddleware):
"""Log all requests with timing and error details."""
async def dispatch(self, request: Request, call_next):
start = time.perf_counter()
method = request.method
path = request.url.path
try:
response = await call_next(request)
duration = time.perf_counter() - start
metrics_registry.observe("rpc_request_duration_seconds", duration)
metrics_registry.increment("rpc_requests_total")
if response.status_code >= 500:
metrics_registry.increment("rpc_server_errors_total")
_app_logger.error("Server error", extra={
"method": method, "path": path,
"status": response.status_code, "duration_ms": round(duration * 1000, 2),
})
elif response.status_code >= 400:
metrics_registry.increment("rpc_client_errors_total")
return response
except Exception as exc:
duration = time.perf_counter() - start
metrics_registry.increment("rpc_unhandled_errors_total")
_app_logger.exception("Unhandled error in request", extra={
"method": method, "path": path, "error": str(exc),
"duration_ms": round(duration * 1000, 2),
})
return JSONResponse(
status_code=503,
content={"detail": "Internal server error"},
)
@asynccontextmanager
async def lifespan(app: FastAPI):
init_db()
init_mempool(
backend=settings.mempool_backend,
db_url=settings.mempool_db_url,
max_size=settings.mempool_max_size,
min_fee=settings.min_fee,
)
_app_logger.info(f"Initializing gossip backend: {settings.gossip_backend}, url: {settings.gossip_broadcast_url}")
backend = create_backend(
settings.gossip_backend,
broadcast_url=settings.gossip_broadcast_url,
)
await gossip_broker.set_backend(backend)
_app_logger.info("Gossip backend initialized successfully")
proposers = []
block_production_override = _env_value(
"AITBC_FORCE_ENABLE_BLOCK_PRODUCTION",
"ENABLE_BLOCK_PRODUCTION",
"enable_block_production",
)
block_production_enabled = settings.enable_block_production
if block_production_override is not None:
block_production_enabled = block_production_override.strip().lower() in {"1", "true", "yes", "on"}
_app_logger.info(f"Block production enabled: {block_production_enabled}, proposer_id: {settings.proposer_id}")
# Initialize PoA proposer for mining integration
if block_production_enabled and settings.proposer_id:
try:
from .consensus import PoAProposer, ProposerConfig
supported_chains = [c.strip() for c in settings.supported_chains.split(",") if c.strip()]
if not supported_chains and settings.chain_id:
supported_chains = [settings.chain_id]
for chain_id in supported_chains:
proposer_config = ProposerConfig(
chain_id=chain_id,
proposer_id=settings.proposer_id,
interval_seconds=settings.block_time_seconds,
max_block_size_bytes=settings.max_block_size_bytes,
max_txs_per_block=settings.max_txs_per_block,
)
proposer = PoAProposer(config=proposer_config, session_factory=session_scope)
# Set the proposer for mining integration
set_poa_proposer(proposer)
# Start the proposer if block production is enabled
asyncio.create_task(proposer.start())
proposers.append(proposer)
_app_logger.info("PoA proposer initialized for mining integration", extra={
"proposer_id": settings.proposer_id,
"supported_chains": supported_chains
})
except Exception as e:
_app_logger.warning(f"Failed to initialize PoA proposer for mining: {e}")
_app_logger.info("Blockchain node started", extra={"supported_chains": settings.supported_chains})
try:
yield
finally:
for proposer in proposers:
try:
await proposer.stop()
except Exception as exc:
_app_logger.warning(f"Failed to stop PoA proposer during shutdown: {exc}")
await gossip_broker.shutdown()
_app_logger.info("Blockchain node stopped")
def create_app() -> FastAPI:
app = FastAPI(title="AITBC Blockchain Node", version="v0.2.2", lifespan=lifespan)
# Middleware (applied in reverse order)
app.add_middleware(RequestLoggingMiddleware)
# Allow higher RPC throughput (sync + node traffic)
app.add_middleware(RateLimitMiddleware, max_requests=5000, window_seconds=60)
app.add_middleware(
CORSMiddleware,
allow_origins=[
"http://localhost:8011", # Coordinator API
"http://localhost:8001", # Exchange API
"http://localhost:8002", # Blockchain Node
"http://localhost:8003", # Blockchain RPC
"http://localhost:8010", # Multimodal GPU
"http://localhost:8011", # GPU Multimodal
"http://localhost:8012", # Modality Optimization
"http://localhost:8013", # Adaptive Learning
"http://localhost:8014", # Marketplace Enhanced
"http://localhost:8015", # hermes Enhanced
"http://localhost:8016", # Web UI
],
allow_methods=["GET", "POST", "OPTIONS"],
allow_headers=["*"],
)
# Include routers
app.include_router(rpc_router, prefix="/rpc", tags=["rpc"])
app.include_router(websocket_router, prefix="/rpc")
# app.include_router(escrow_router, prefix="/rpc") # Disabled until escrow routes are implemented
# Metrics and health endpoints
metrics_router = APIRouter()
@metrics_router.get("/metrics", response_class=PlainTextResponse, tags=["metrics"], summary="Prometheus metrics")
async def metrics() -> str:
return metrics_registry.render_prometheus()
@metrics_router.get("/health", tags=["health"], summary="Health check")
async def health() -> dict:
return {
"status": "ok",
"supported_chains": [c.strip() for c in settings.supported_chains.split(",") if c.strip()],
"proposer_id": settings.proposer_id,
}
app.include_router(metrics_router)
return app
app = create_app()