docs: add structure.md, update files.md, rewrite README for GitHub, add favicon, replace debug prints with logging, remove stale src/ copy and empty dirs

This commit is contained in:
oib
2026-02-11 21:14:56 +01:00
parent 6fcc573db7
commit 5773156ce1
15 changed files with 523 additions and 739 deletions

11
.gitignore vendored
View File

@@ -82,6 +82,12 @@ pids/
.classpath
.settings/
# ===================
# Runtime / PID files
# ===================
*.pid
apps/.service_pids
# ===================
# OS Files
# ===================
@@ -143,6 +149,11 @@ home/client/client_wallet.json
home/genesis_wallet.json
home/miner/miner_wallet.json
# ===================
# Stale source copies
# ===================
src/aitbc_chain/
# ===================
# Project Specific
# ===================

157
README.md
View File

@@ -1,36 +1,143 @@
# AITBC Monorepo
# AITBC — AI Token Blockchain
This repository houses all components of the Artificial Intelligence Token Blockchain (AITBC) stack, including coordinator services, blockchain node, miner daemon, client-facing web apps, SDKs, and documentation.
Decentralized GPU compute marketplace with blockchain-based job coordination, Ollama inference, ZK receipt verification, and token payments.
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
## Overview
AITBC is a full-stack blockchain platform that connects GPU compute providers (miners) with AI workload consumers (clients) through a decentralized marketplace. The system handles job submission, miner matching, GPU inference execution, cryptographic receipt generation, and on-chain payment settlement.
**Key capabilities:**
- **Blockchain nodes** — PoA consensus, gossip relay, WebSocket RPC
- **Coordinator API** — Job lifecycle, miner registry, marketplace, multi-tenancy
- **GPU mining** — Ollama-based LLM inference with host GPU passthrough
- **Wallet daemon** — Balance tracking, receipt verification, ledger management
- **Trade exchange** — Bitcoin/AITBC trading with order book and price ticker
- **ZK circuits** — Zero-knowledge receipt verification (Circom/snarkjs)
- **Browser wallet** — Firefox extension for AITBC transactions
- **Explorer** — Real-time blockchain explorer (blocks, transactions, addresses, receipts)
## Architecture
```
Client ──► Coordinator API ──► Pool Hub ──► Miner (GPU/Ollama)
│ │
▼ ▼
Marketplace ZK Receipt
│ │
▼ ▼
Wallet Daemon ◄──── Blockchain Nodes ◄─── Settlement
Trade Exchange
```
## Directory Structure
```
aitbc/
├── apps/ # Main applications (coordinator-api, blockchain-node, etc.)
├── packages/ # Shared packages and SDKs
├── scripts/ # Utility scripts
── testing/ # Test scripts and utilities
├── tests/ # Pytest test suites
├── docs/ # Documentation
│ ├── guides/ # Development guides
── reports/ # Generated reports and summaries
├── infra/ # Infrastructure and deployment configs
├── dev-utils/ # Development utilities
── .windsurf/ # Windsurf IDE configuration
├── apps/ # Core microservices
│ ├── blockchain-node/ # PoA blockchain node (FastAPI + gossip)
│ ├── coordinator-api/ # Job coordination API (FastAPI)
── explorer-web/ # Blockchain explorer (TypeScript + Vite)
│ ├── marketplace-web/ # GPU marketplace frontend (TypeScript + Vite)
│ ├── pool-hub/ # Mining pool management (FastAPI + Redis)
│ ├── trade-exchange/ # BTC/AITBC exchange (FastAPI + WebSocket)
── wallet-daemon/ # Wallet service (FastAPI)
│ └── zk-circuits/ # ZK proof circuits (Circom)
├── cli/ # CLI tools (client, miner, wallet)
── contracts/ # Solidity smart contracts
├── docs/ # Documentation (structure, guides, reference, reports)
├── extensions/ # Browser extensions (Firefox wallet)
├── home/ # Local simulation scripts
├── infra/ # Infrastructure (nginx, k8s, helm, terraform)
├── packages/ # Shared libraries
│ ├── py/aitbc-crypto/ # Cryptographic primitives
│ ├── py/aitbc-sdk/ # Python SDK
│ └── solidity/aitbc-token/# ERC-20 token contract
├── plugins/ollama/ # Ollama LLM integration
├── scripts/ # Deployment, GPU, service, and test scripts
├── systemd/ # Systemd service units
├── tests/ # Test suites (unit, integration, e2e, security, load)
└── website/ # Public website and HTML documentation
```
## Getting Started
See [docs/structure.md](docs/structure.md) for detailed file-level documentation.
1. Review the bootstrap documents under `docs/bootstrap/` to understand stage-specific goals.
2. Fill in service-specific READMEs located under `apps/` and `packages/` as the implementations progress.
3. Use the provided directory scaffold as the starting point for coding each subsystem.
4. Explore the new Python receipt SDK under `packages/py/aitbc-sdk/` for helpers to fetch and verify coordinator receipts (see `docs/run.md` for examples).
5. Run `scripts/ci/run_python_tests.sh` (via Poetry) to execute coordinator, SDK, miner-node, and wallet-daemon test suites before submitting changes.
6. GitHub Actions (`.github/workflows/python-tests.yml`) automatically runs the same script on pushes and pull requests targeting `main`.
## Quick Start
## Testing
### Prerequisites
- Test scripts are located in `scripts/testing/`
- Run individual tests: `python3 scripts/testing/test_block_import.py`
- Run pytest suite: `pytest tests/`
- See `docs/guides/WINDSURF_TESTING_GUIDE.md` for detailed testing instructions
- Python 3.11+
- Node.js 18+ (for web apps and ZK circuits)
- PostgreSQL (optional, SQLite for development)
- Ollama (for GPU inference)
### Run Services Locally
```bash
# Start all services
./scripts/dev_services.sh
# Or start individually
cd apps/coordinator-api && uvicorn app.main:app --port 8000
cd apps/blockchain-node && python -m aitbc_chain.main
cd apps/wallet-daemon && uvicorn app.main:app --port 8002
```
### Run Tests
```bash
# Full test suite
pytest tests/
# Unit tests only
pytest tests/unit/
# Integration tests
pytest tests/integration/
# CI script (all apps)
./scripts/ci/run_python_tests.sh
```
### CLI Usage
```bash
# Submit a job as a client
python cli/client.py submit --model llama3 --prompt "Hello world"
# Start mining
python cli/miner.py start --gpu 0
# Check wallet balance
python cli/wallet.py balance
```
## Deployment
Services run in an Incus container with systemd units. See `systemd/` for service definitions and `scripts/deploy/` for deployment automation.
```bash
# Deploy to container
./scripts/deploy/deploy-to-container.sh
# Deploy blockchain nodes
./scripts/deploy/deploy-blockchain.sh
```
## Documentation
| Document | Description |
|----------|-------------|
| [docs/structure.md](docs/structure.md) | Codebase structure and file layout |
| [docs/files.md](docs/files.md) | File audit and status tracking |
| [docs/roadmap.md](docs/roadmap.md) | Development roadmap |
| [docs/components.md](docs/components.md) | Component overview |
| [docs/infrastructure.md](docs/infrastructure.md) | Infrastructure guide |
| [docs/full-documentation.md](docs/full-documentation.md) | Complete technical documentation |
## License
[MIT](LICENSE) — Copyright (c) 2025 AITBC

View File

@@ -1 +0,0 @@
1529925 1529926 1529927 1529928

View File

@@ -8,6 +8,9 @@ import uuid
import time
import json
import os
import logging
logger = logging.getLogger(__name__)
from ..schemas import ExchangePaymentRequest, ExchangePaymentResponse
from ..services.bitcoin_wallet import get_wallet_balance, get_wallet_info
@@ -109,7 +112,7 @@ async def confirm_payment(
from ..services.blockchain import mint_tokens
mint_tokens(payment['user_id'], payment['aitbc_amount'])
except Exception as e:
print(f"Error minting tokens: {e}")
logger.error("Error minting tokens: %s", e)
# In production, handle this error properly
return {

View File

@@ -7,6 +7,9 @@ from pydantic import BaseModel, Field
from typing import Optional, Dict, Any, List
from datetime import datetime, timedelta
import json
import logging
logger = logging.getLogger(__name__)
from ..schemas import UserProfile
from ..storage import SessionDep
@@ -349,7 +352,7 @@ async def execute_parameter_change(target: Dict[str, Any], background_tasks):
"""Execute a parameter change proposal"""
# This would update system parameters
print(f"Executing parameter change: {target}")
logger.info("Executing parameter change: %s", target)
# Implementation would depend on the specific parameters
@@ -357,7 +360,7 @@ async def execute_protocol_upgrade(target: Dict[str, Any], background_tasks):
"""Execute a protocol upgrade proposal"""
# This would trigger a protocol upgrade
print(f"Executing protocol upgrade: {target}")
logger.info("Executing protocol upgrade: %s", target)
# Implementation would involve coordinating with nodes
@@ -365,7 +368,7 @@ async def execute_fund_allocation(target: Dict[str, Any], background_tasks):
"""Execute a fund allocation proposal"""
# This would transfer funds from treasury
print(f"Executing fund allocation: {target}")
logger.info("Executing fund allocation: %s", target)
# Implementation would involve treasury management
@@ -373,7 +376,7 @@ async def execute_policy_change(target: Dict[str, Any], background_tasks):
"""Execute a policy change proposal"""
# This would update system policies
print(f"Executing policy change: {target}")
logger.info("Executing policy change: %s", target)
# Implementation would depend on the specific policy

View File

@@ -6,9 +6,12 @@ Uses RPC to connect to Bitcoin Core (or alternative like Block.io)
import os
import json
import logging
import requests
from typing import Dict, Optional
logger = logging.getLogger(__name__)
# Bitcoin wallet configuration
WALLET_CONFIG = {
# For development, we'll use testnet
@@ -31,11 +34,11 @@ class BitcoinWallet:
try:
result = self._rpc_call('getbalance', ["*", 0, False])
if result.get('error') is not None:
print(f"Bitcoin RPC error: {result['error']}")
logger.error("Bitcoin RPC error: %s", result['error'])
return 0.0
return result.get('result', 0.0)
except Exception as e:
print(f"Failed to get balance: {e}")
logger.error("Failed to get balance: %s", e)
return 0.0
def get_new_address(self) -> str:
@@ -43,11 +46,11 @@ class BitcoinWallet:
try:
result = self._rpc_call('getnewaddress', ["", "bech32"])
if result.get('error') is not None:
print(f"Bitcoin RPC error: {result['error']}")
logger.error("Bitcoin RPC error: %s", result['error'])
return self.config['fallback_address']
return result.get('result', self.config['fallback_address'])
except Exception as e:
print(f"Failed to get new address: {e}")
logger.error("Failed to get new address: %s", e)
return self.config['fallback_address']
def list_transactions(self, count: int = 10) -> list:
@@ -55,11 +58,11 @@ class BitcoinWallet:
try:
result = self._rpc_call('listtransactions', ["*", count, 0, True])
if result.get('error') is not None:
print(f"Bitcoin RPC error: {result['error']}")
logger.error("Bitcoin RPC error: %s", result['error'])
return []
return result.get('result', [])
except Exception as e:
print(f"Failed to list transactions: {e}")
logger.error("Failed to list transactions: %s", e)
return []
def _rpc_call(self, method: str, params: list = None) -> Dict:
@@ -83,7 +86,7 @@ class BitcoinWallet:
response.raise_for_status()
return response.json()
except Exception as e:
print(f"RPC call failed: {e}")
logger.error("RPC call failed: %s", e)
return {"error": str(e)}
# Create a wallet instance
@@ -117,7 +120,7 @@ def get_wallet_info() -> Dict[str, any]:
"blocks": blockchain_info.get('result', {}).get('blocks', 0) if is_connected else 0
}
except Exception as e:
print(f"Error getting wallet info: {e}")
logger.error("Error getting wallet info: %s", e)
return {
"balance": 0.0,
"address": "tb1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",

View File

@@ -4,10 +4,13 @@ Blockchain service for AITBC token operations
import httpx
import asyncio
import logging
from typing import Optional
from ..config import settings
logger = logging.getLogger(__name__)
BLOCKCHAIN_RPC = f"http://127.0.0.1:9080/rpc"
async def mint_tokens(address: str, amount: float) -> dict:
@@ -44,6 +47,6 @@ def get_balance(address: str) -> Optional[float]:
return float(data.get("balance", 0))
except Exception as e:
print(f"Error getting balance: {e}")
logger.error("Error getting balance: %s", e)
return None

View File

@@ -1,9 +1,12 @@
from __future__ import annotations
import logging
from typing import Any, Dict, Optional
from secrets import token_hex
from datetime import datetime
logger = logging.getLogger(__name__)
from aitbc_crypto.signing import ReceiptSigner
from sqlmodel import Session
@@ -129,7 +132,7 @@ class ReceiptService:
except Exception as e:
# Log error but don't fail receipt creation
print(f"Failed to generate ZK proof: {e}")
logger.warning("Failed to generate ZK proof: %s", e)
receipt_row = JobReceipt(job_id=job.id, receipt_id=payload["receipt_id"], payload=payload)
self.session.add(receipt_row)

View File

@@ -8,9 +8,12 @@ import psycopg2
from psycopg2.extras import RealDictCursor
from typing import Generator, Optional, Dict, Any, List
import json
import logging
from datetime import datetime
from decimal import Decimal
logger = logging.getLogger(__name__)
from .config_pg import settings
# SQLAlchemy setup for complex queries
@@ -203,7 +206,7 @@ def init_db():
# Create all tables
Base.metadata.create_all(bind=engine)
print("PostgreSQL database initialized successfully!")
logger.info("PostgreSQL database initialized successfully")
# Health check
def check_db_health() -> Dict[str, Any]:

View File

@@ -5,7 +5,7 @@ This document categorizes all files and folders in the repository by their statu
- **Greylist (⚠️)**: Uncertain status, may need review
- **Blacklist (❌)**: Legacy, unused, outdated, candidates for removal
Last updated: 2026-01-29
Last updated: 2026-02-11
---
@@ -65,14 +65,6 @@ Last updated: 2026-01-29
| `docs/guides/` | ✅ Active | Development guides (moved from root) |
| `docs/reports/` | ✅ Active | Generated reports (moved from root) |
### Cascade Skills (`.windsurf/`)
| Path | Status | Notes |
|------|--------|-------|
| `.windsurf/skills/blockchain-operations/` | ✅ Active | Node management skill |
| `.windsurf/skills/deploy-production/` | ✅ Active | Deployment skill |
| `.windsurf/workflows/` | ✅ Active | Workflow definitions |
### CLI Tools (`cli/`)
| Path | Status | Notes |
@@ -264,14 +256,82 @@ These empty folders are intentional scaffolding for planned future work per the
---
## Issues Found (2026-02-11)
### Empty Directories (Delete)
| Path | Action |
|------|--------|
| `apps/blockchain-node/src/aitbc_chain/ledger/` | Delete — empty placeholder, never implemented |
| `apps/blockchain-node/src/aitbc_chain/mempool/` | Delete — empty dir, mempool logic is in `mempool.py` |
| `apps/coordinator-api/src/app/ws/` | Delete — empty WebSocket placeholder, never implemented |
| `apps/explorer-web/public/js/components/` | Delete — empty, TS components are in `src/components/` |
| `apps/explorer-web/public/js/pages/` | Delete — empty, TS pages are in `src/pages/` |
| `apps/explorer-web/public/js/vendors/` | Delete — empty vendor dir |
| `apps/explorer-web/public/assets/` | Delete — empty assets dir |
| `packages/py/aitbc-crypto/build/bdist.linux-x86_64/` | Delete — build artifact |
### Files in Wrong Location (Move)
| Current Path | Correct Path | Reason |
|-------------|-------------|--------|
| `apps/coordinator-api/coordinator.db` | gitignored / `data/` | SQLite database should not be in git |
| `apps/coordinator-api/.env` | gitignored | Environment file with secrets, should not be in git |
| `apps/.service_pids` | gitignored | Runtime PID file, should not be in git |
| `src/aitbc_chain/` | `apps/blockchain-node/src/aitbc_chain/` | Duplicate/stale copy of blockchain node source |
| `website/docs-clients.html` | `website/docs/docs-clients.html` | Inconsistent location, duplicate of file in `docs/` |
| `website/docs-developers.html` | `website/docs/docs-developers.html` | Inconsistent location, duplicate of file in `docs/` |
| `website/docs-miners.html` | `website/docs/docs-miners.html` | Inconsistent location, duplicate of file in `docs/` |
| `website/docs-index.html` | `website/docs/index.html` | Inconsistent location, duplicate of file in `docs/` |
### Legacy Files (Delete)
| Path | Reason |
|------|--------|
| `SECURITY_CLEANUP_GUIDE.md` | One-time cleanup guide, already completed |
| `apps/trade-exchange/index_working.html` | Backup copy of `index.html` |
| `apps/trade-exchange/index.prod.html` | Superseded by `build.py` production build |
| `apps/trade-exchange/index.real.html` | Superseded by `build.py` production build |
| `tests/conftest_fixtures.py` | Unused alternate conftest |
| `tests/conftest_full.py` | Unused alternate conftest |
| `tests/conftest_path.py` | Unused alternate conftest |
| `tests/pytest_simple.ini` | Duplicate of root `pytest.ini` |
| `tests/test_blockchain_simple.py` | Superseded by `test_blockchain_nodes.py` |
| `tests/test_blockchain_final.py` | Superseded by `test_blockchain_nodes.py` |
| `tests/test_discovery.py` | One-time discovery script |
| `tests/test_windsurf_integration.py` | IDE-specific test, not for GitHub |
| `scripts/exchange-router-fixed.py` | One-time fix script |
| `scripts/start_mock_blockchain.sh` | Superseded by `tests/mock_blockchain_node.py` |
| `apps/marketplace-web/src/counter.ts` | Vite template boilerplate, unused |
| `apps/marketplace-web/src/typescript.svg` | Vite template boilerplate, unused |
| `apps/marketplace-web/public/vite.svg` | Vite template boilerplate, unused |
| `.vscode/` | IDE-specific, should be gitignored |
### Debug Print Statements (Replace with logging)
| File | Lines | Statement |
|------|-------|-----------|
| `apps/coordinator-api/src/app/routers/exchange.py` | 112 | `print(f"Error minting tokens: {e}")` |
| `apps/coordinator-api/src/app/routers/governance.py` | 352-376 | 4x `print(f"Executing ...")` |
| `apps/coordinator-api/src/app/services/receipts.py` | 132 | `print(f"Failed to generate ZK proof: {e}")` |
| `apps/coordinator-api/src/app/services/blockchain.py` | 47 | `print(f"Error getting balance: {e}")` |
| `apps/coordinator-api/src/app/services/bitcoin_wallet.py` | 34-134 | 8x `print(...)` debug statements |
| `apps/coordinator-api/src/app/storage/db_pg.py` | 206 | `print("✅ PostgreSQL database initialized successfully!")` |
---
## Summary Statistics
| Category | Count | Action |
|----------|-------|--------|
| **Whitelist ✅** | ~60 items | Keep and maintain |
| **Greylist ⚠️** | 0 items | All resolved! |
| **Placeholders 📋** | 12 folders | Fill per roadmap Stage 19 |
| **Greylist ⚠️** | 0 items | All resolved |
| **Placeholders 📋** | 12 folders | Fill per roadmap |
| **Removed ❌** | 35 items | Cleaned up 2026-01-24 |
| **Empty dirs** | 8 dirs | Delete |
| **Misplaced files** | 8 files | Move or gitignore |
| **Legacy files** | 18 files | Delete |
| **Debug prints** | 17 statements | Replace with logger |
### Completed Actions (2026-01-24)

265
docs/structure.md Normal file
View File

@@ -0,0 +1,265 @@
# AITBC Codebase Structure
> Monorepo layout for the AI Token Blockchain platform.
## Top-Level Overview
```
aitbc/
├── apps/ # Core microservices and web applications
├── assets/ # Shared frontend assets (CSS, JS, fonts)
├── cli/ # Command-line interface tools
├── contracts/ # Solidity smart contracts (standalone)
├── dev-utils/ # Developer utilities and path configs
├── docs/ # Markdown documentation
├── examples/ # Usage examples and demos
├── extensions/ # Browser extensions (Firefox wallet)
├── home/ # Local workflow scripts (client/miner simulation)
├── infra/ # Infrastructure configs (nginx, k8s, terraform, helm)
├── packages/ # Shared libraries and SDKs
├── plugins/ # Plugin integrations (Ollama)
├── protocols/ # Protocol specs and sample data
├── scripts/ # Operational and deployment scripts
├── src/ # Shared Python source (cross-site sync, RPC)
├── systemd/ # Systemd service unit files
├── tests/ # Pytest test suites (unit, integration, e2e, security, load)
├── website/ # Public-facing website and HTML documentation
├── .gitignore
├── .editorconfig
├── LICENSE # MIT License
├── pyproject.toml # Python project configuration
├── pytest.ini # Pytest settings and markers
└── README.md
```
---
## apps/ — Core Applications
### blockchain-node
Full blockchain node implementation with PoA consensus, gossip relay, mempool, RPC API, WebSocket support, and observability dashboards.
```
apps/blockchain-node/
├── src/aitbc_chain/
│ ├── app.py # FastAPI application
│ ├── main.py # Entry point
│ ├── config.py # Node configuration
│ ├── database.py # Chain storage
│ ├── models.py # Block/Transaction models
│ ├── mempool.py # Transaction mempool
│ ├── metrics.py # Prometheus metrics
│ ├── logger.py # Structured logging
│ ├── consensus/poa.py # Proof-of-Authority consensus
│ ├── gossip/ # P2P gossip protocol (broker, relay)
│ ├── observability/ # Dashboards and exporters
│ └── rpc/ # JSON-RPC router and WebSocket
├── scripts/ # Genesis creation, key generation, benchmarks
├── tests/ # Unit tests (models, gossip, WebSocket, observability)
└── pyproject.toml
```
### coordinator-api
Central job coordination API with marketplace, payments, ZK proofs, multi-tenancy, and governance.
```
apps/coordinator-api/
├── src/app/
│ ├── main.py # FastAPI entry point
│ ├── config.py # Configuration
│ ├── database.py # Database setup
│ ├── deps.py # Dependency injection
│ ├── exceptions.py # Custom exceptions
│ ├── logging.py # Logging config
│ ├── metrics.py # Prometheus metrics
│ ├── domain/ # Domain models (job, miner, payment, user, marketplace)
│ ├── models/ # DB models (registry, confidential, multitenant, services)
│ ├── routers/ # API endpoints (admin, client, miner, marketplace, payments, governance, exchange, explorer, ZK)
│ ├── services/ # Business logic (jobs, miners, payments, receipts, ZK proofs, encryption, HSM, blockchain, bitcoin wallet)
│ ├── storage/ # Database adapters (SQLite, PostgreSQL)
│ ├── middleware/ # Tenant context middleware
│ ├── repositories/ # Data access layer
│ └── schemas/ # Pydantic schemas
├── aitbc/settlement/ # Cross-chain settlement (LayerZero bridge)
├── migrations/ # SQL migrations (schema, indexes, data, payments)
├── scripts/ # PostgreSQL migration scripts
├── tests/ # API tests (jobs, marketplace, ZK, receipts, miners)
└── pyproject.toml
```
### explorer-web
Blockchain explorer SPA built with TypeScript and Vite.
```
apps/explorer-web/
├── src/
│ ├── main.ts # Application entry
│ ├── config.ts # API configuration
│ ├── components/ # UI components (header, footer, data mode toggle, notifications)
│ ├── lib/ # Data models and mock data
│ └── pages/ # Page views (overview, blocks, transactions, addresses, receipts)
├── public/ # Static assets (CSS themes, mock JSON data)
├── tests/e2e/ # Playwright end-to-end tests
├── vite.config.ts
└── tsconfig.json
```
### marketplace-web
GPU compute marketplace frontend built with TypeScript and Vite.
```
apps/marketplace-web/
├── src/
│ ├── main.ts # Application entry
│ ├── lib/ # API client and auth
│ └── style.css # Styles
├── public/ # Mock data (offers, stats)
├── vite.config.ts
└── tsconfig.json
```
### wallet-daemon
Wallet service with receipt verification and ledger management.
```
apps/wallet-daemon/
├── src/app/
│ ├── main.py # FastAPI entry point
│ ├── settings.py # Configuration
│ ├── ledger_mock/ # Mock ledger with PostgreSQL adapter
│ └── receipts/ # Receipt verification service
├── scripts/ # PostgreSQL migration
├── tests/ # Wallet API and receipt tests
└── pyproject.toml
```
### trade-exchange
Bitcoin/AITBC trading exchange with order book, price ticker, and admin panel.
```
apps/trade-exchange/
├── server.py # WebSocket price server
├── simple_exchange_api.py # Exchange REST API (SQLite)
├── simple_exchange_api_pg.py # Exchange REST API (PostgreSQL)
├── exchange_api.py # Full exchange API
├── bitcoin-wallet.py # Bitcoin wallet integration
├── database.py # Database layer
├── build.py # Production build script
├── index.html # Exchange frontend
├── admin.html # Admin panel
└── scripts/ # PostgreSQL migration
```
### pool-hub
Mining pool management with job matching, miner scoring, and Redis caching.
```
apps/pool-hub/
├── src/
│ ├── app/ # Legacy app structure (routers, registry, scoring)
│ └── poolhub/ # Current app (routers, models, repositories, services, Redis)
├── migrations/ # Alembic migrations
└── tests/ # API and repository tests
```
### zk-circuits
Zero-knowledge proof circuits for receipt verification.
```
apps/zk-circuits/
├── circuits/receipt.circom # Circom circuit definition
├── generate_proof.js # Proof generation
├── test.js # Circuit tests
└── benchmark.js # Performance benchmarks
```
---
## packages/ — Shared Libraries
```
packages/
├── py/
│ ├── aitbc-crypto/ # Cryptographic primitives (signing, hashing, key derivation)
│ └── aitbc-sdk/ # Python SDK for coordinator API (receipt fetching/verification)
└── solidity/
└── aitbc-token/ # ERC-20 AITBC token contract with Hardhat tooling
```
---
## scripts/ — Operations
```
scripts/
├── aitbc-cli.sh # Main CLI entry point
├── deploy/ # Deployment scripts (container, remote, blockchain, explorer, exchange, nginx)
├── gpu/ # GPU miner management (host miner, registry, exchange integration)
├── service/ # Service lifecycle (start, stop, diagnose, fix)
├── testing/ # Test runners and verification scripts
├── test/ # Individual test scripts (coordinator, GPU, explorer)
├── ci/ # CI pipeline scripts
├── ops/ # Operational scripts (systemd install)
└── dev/ # Development tools (WebSocket load test)
```
---
## infra/ — Infrastructure
```
infra/
├── nginx/ # Nginx configs (reverse proxy, local, production)
├── k8s/ # Kubernetes manifests (backup, cert-manager, network policies, sealed secrets)
├── helm/ # Helm charts (coordinator deployment, values per environment)
├── terraform/ # Terraform modules (Kubernetes cluster, environments: dev/staging/prod)
└── scripts/ # Infra scripts (backup, restore, chaos testing)
```
---
## tests/ — Test Suites
```
tests/
├── unit/ # Unit tests (blockchain node, coordinator API, wallet daemon)
├── integration/ # Integration tests (blockchain node, full workflow)
├── e2e/ # End-to-end tests (user scenarios, wallet daemon)
├── security/ # Security tests (confidential transactions, comprehensive audit)
├── load/ # Load tests (Locust)
├── conftest.py # Shared pytest fixtures
└── test_blockchain_nodes.py # Live node connectivity tests
```
---
## website/ — Public Website
```
website/
├── index.html # Landing page
├── 404.html # Error page
├── docs/ # HTML documentation (per-component pages, CSS, JS)
├── dashboards/ # Admin and miner dashboards
├── BrowserWallet/ # Browser wallet interface
├── extensions/ # Packaged browser extensions (.zip, .xpi)
└── aitbc-proxy.conf # Nginx proxy config for website
```
---
## Other Directories
| Directory | Purpose |
|-----------|---------|
| `cli/` | CLI tools for client, miner, wallet operations and GPU testing |
| `plugins/ollama/` | Ollama LLM integration (client plugin, miner plugin, service layer) |
| `home/` | Local simulation scripts for client/miner workflows |
| `extensions/` | Firefox wallet extension source code |
| `contracts/` | Standalone Solidity contracts (ZKReceiptVerifier) |
| `protocols/` | Protocol sample data (signed receipts) |
| `src/` | Shared Python modules (cross-site sync, RPC router) |
| `systemd/` | Systemd unit files for all AITBC services |
| `dev-utils/` | Developer utilities (Python path config) |
| `docs/` | Markdown documentation (guides, reports, reference, tutorials, operator docs) |
| `assets/` | Shared frontend assets (Tailwind CSS, FontAwesome, Lucide icons, Axios) |

View File

@@ -1,457 +0,0 @@
from __future__ import annotations
import asyncio
import hashlib
import json
import time
from datetime import datetime
from typing import Any, Dict, Optional, List
from fastapi import APIRouter, HTTPException, status
from pydantic import BaseModel, Field, model_validator
from sqlmodel import select
from ..config import settings
from ..database import session_scope
from ..gossip import gossip_broker
from ..mempool import get_mempool
from ..metrics import metrics_registry
from ..models import Account, Block, Receipt, Transaction
router = APIRouter()
def _serialize_receipt(receipt: Receipt) -> Dict[str, Any]:
return {
"receipt_id": receipt.receipt_id,
"job_id": receipt.job_id,
"payload": receipt.payload,
"miner_signature": receipt.miner_signature,
"coordinator_attestations": receipt.coordinator_attestations,
"minted_amount": receipt.minted_amount,
"recorded_at": receipt.recorded_at.isoformat(),
}
class TransactionRequest(BaseModel):
type: str = Field(description="Transaction type, e.g. TRANSFER or RECEIPT_CLAIM")
sender: str
nonce: int
fee: int = Field(ge=0)
payload: Dict[str, Any]
sig: Optional[str] = Field(default=None, description="Signature payload")
@model_validator(mode="after")
def normalize_type(self) -> "TransactionRequest": # type: ignore[override]
normalized = self.type.upper()
if normalized not in {"TRANSFER", "RECEIPT_CLAIM"}:
raise ValueError(f"unsupported transaction type: {self.type}")
self.type = normalized
return self
class ReceiptSubmissionRequest(BaseModel):
sender: str
nonce: int
fee: int = Field(ge=0)
payload: Dict[str, Any]
sig: Optional[str] = None
class EstimateFeeRequest(BaseModel):
type: Optional[str] = None
payload: Dict[str, Any] = Field(default_factory=dict)
class MintFaucetRequest(BaseModel):
address: str
amount: int = Field(gt=0)
@router.get("/head", summary="Get current chain head")
async def get_head() -> Dict[str, Any]:
metrics_registry.increment("rpc_get_head_total")
start = time.perf_counter()
with session_scope() as session:
result = session.exec(select(Block).order_by(Block.height.desc()).limit(1)).first()
if result is None:
metrics_registry.increment("rpc_get_head_not_found_total")
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="no blocks yet")
metrics_registry.increment("rpc_get_head_success_total")
metrics_registry.observe("rpc_get_head_duration_seconds", time.perf_counter() - start)
return {
"height": result.height,
"hash": result.hash,
"timestamp": result.timestamp.isoformat(),
"tx_count": result.tx_count,
}
@router.get("/blocks/{height}", summary="Get block by height")
async def get_block(height: int) -> Dict[str, Any]:
metrics_registry.increment("rpc_get_block_total")
start = time.perf_counter()
with session_scope() as session:
block = session.exec(select(Block).where(Block.height == height)).first()
if block is None:
metrics_registry.increment("rpc_get_block_not_found_total")
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="block not found")
metrics_registry.increment("rpc_get_block_success_total")
metrics_registry.observe("rpc_get_block_duration_seconds", time.perf_counter() - start)
return {
"proposer": block.proposer,
"proposer": block.proposer,
"height": block.height,
"hash": block.hash,
"parent_hash": block.parent_hash,
"timestamp": block.timestamp.isoformat(),
"tx_count": block.tx_count,
"state_root": block.state_root,
}
@router.get("/blocks-range", summary="Get blocks in range")
async def get_blocks_range(start_height: int = 0, end_height: int = 100, limit: int = 1000) -> List[Dict[str, Any]]:
metrics_registry.increment("rpc_get_blocks_range_total")
start = time.perf_counter()
# Validate parameters
if limit > 10000:
limit = 10000
if end_height - start_height > limit:
end_height = start_height + limit
with session_scope() as session:
stmt = (
select(Block)
.where(Block.height >= start_height)
.where(Block.height <= end_height)
.order_by(Block.height)
)
blocks = session.exec(stmt).all()
metrics_registry.observe("rpc_get_blocks_range_duration_seconds", time.perf_counter() - start)
return [
{
"proposer": block.proposer,
"proposer": block.proposer,
"height": block.height,
"hash": block.hash,
"parent_hash": block.parent_hash,
"timestamp": block.timestamp.isoformat(),
"tx_count": block.tx_count,
"state_root": block.state_root,
}
for block in blocks
]
@router.get("/tx/{tx_hash}", summary="Get transaction by hash")
async def get_transaction(tx_hash: str) -> Dict[str, Any]:
metrics_registry.increment("rpc_get_transaction_total")
start = time.perf_counter()
with session_scope() as session:
tx = session.exec(select(Transaction).where(Transaction.tx_hash == tx_hash)).first()
if tx is None:
metrics_registry.increment("rpc_get_transaction_not_found_total")
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="transaction not found")
metrics_registry.increment("rpc_get_transaction_success_total")
metrics_registry.observe("rpc_get_transaction_duration_seconds", time.perf_counter() - start)
return {
"tx_hash": tx.tx_hash,
"block_height": tx.block_height,
"sender": tx.sender,
"recipient": tx.recipient,
"payload": tx.payload,
"created_at": tx.created_at.isoformat(),
}
@router.get("/receipts/{receipt_id}", summary="Get receipt by ID")
async def get_receipt(receipt_id: str) -> Dict[str, Any]:
metrics_registry.increment("rpc_get_receipt_total")
start = time.perf_counter()
with session_scope() as session:
receipt = session.exec(select(Receipt).where(Receipt.receipt_id == receipt_id)).first()
if receipt is None:
metrics_registry.increment("rpc_get_receipt_not_found_total")
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="receipt not found")
metrics_registry.increment("rpc_get_receipt_success_total")
metrics_registry.observe("rpc_get_receipt_duration_seconds", time.perf_counter() - start)
return _serialize_receipt(receipt)
@router.get("/getBalance/{address}", summary="Get account balance")
async def get_balance(address: str) -> Dict[str, Any]:
metrics_registry.increment("rpc_get_balance_total")
start = time.perf_counter()
with session_scope() as session:
account = session.get(Account, address)
if account is None:
metrics_registry.increment("rpc_get_balance_empty_total")
metrics_registry.observe("rpc_get_balance_duration_seconds", time.perf_counter() - start)
return {"address": address, "balance": 0, "nonce": 0}
metrics_registry.increment("rpc_get_balance_success_total")
metrics_registry.observe("rpc_get_balance_duration_seconds", time.perf_counter() - start)
return {
"address": account.address,
"balance": account.balance,
"nonce": account.nonce,
"updated_at": account.updated_at.isoformat(),
}
@router.post("/sendTx", summary="Submit a new transaction")
async def send_transaction(request: TransactionRequest) -> Dict[str, Any]:
metrics_registry.increment("rpc_send_tx_total")
start = time.perf_counter()
mempool = get_mempool()
tx_dict = request.model_dump()
tx_hash = mempool.add(tx_dict)
try:
asyncio.create_task(
gossip_broker.publish(
"transactions",
{
"tx_hash": tx_hash,
"sender": request.sender,
"payload": request.payload,
"nonce": request.nonce,
"fee": request.fee,
"type": request.type,
},
)
)
metrics_registry.increment("rpc_send_tx_success_total")
return {"tx_hash": tx_hash}
except Exception:
metrics_registry.increment("rpc_send_tx_failed_total")
raise
finally:
metrics_registry.observe("rpc_send_tx_duration_seconds", time.perf_counter() - start)
@router.post("/submitReceipt", summary="Submit receipt claim transaction")
async def submit_receipt(request: ReceiptSubmissionRequest) -> Dict[str, Any]:
metrics_registry.increment("rpc_submit_receipt_total")
start = time.perf_counter()
tx_payload = {
"type": "RECEIPT_CLAIM",
"sender": request.sender,
"nonce": request.nonce,
"fee": request.fee,
"payload": request.payload,
"sig": request.sig,
}
tx_request = TransactionRequest.model_validate(tx_payload)
try:
response = await send_transaction(tx_request)
metrics_registry.increment("rpc_submit_receipt_success_total")
return response
except HTTPException:
metrics_registry.increment("rpc_submit_receipt_failed_total")
raise
except Exception:
metrics_registry.increment("rpc_submit_receipt_failed_total")
raise
finally:
metrics_registry.observe("rpc_submit_receipt_duration_seconds", time.perf_counter() - start)
@router.post("/estimateFee", summary="Estimate transaction fee")
async def estimate_fee(request: EstimateFeeRequest) -> Dict[str, Any]:
metrics_registry.increment("rpc_estimate_fee_total")
start = time.perf_counter()
base_fee = 10
per_byte = 1
payload_bytes = len(json.dumps(request.payload, sort_keys=True, separators=(",", ":")).encode())
estimated_fee = base_fee + per_byte * payload_bytes
tx_type = (request.type or "TRANSFER").upper()
metrics_registry.increment("rpc_estimate_fee_success_total")
metrics_registry.observe("rpc_estimate_fee_duration_seconds", time.perf_counter() - start)
return {
"type": tx_type,
"base_fee": base_fee,
"payload_bytes": payload_bytes,
"estimated_fee": estimated_fee,
}
@router.post("/admin/mintFaucet", summary="Mint devnet funds to an address")
async def mint_faucet(request: MintFaucetRequest) -> Dict[str, Any]:
metrics_registry.increment("rpc_mint_faucet_total")
start = time.perf_counter()
with session_scope() as session:
account = session.get(Account, request.address)
if account is None:
account = Account(address=request.address, balance=request.amount)
session.add(account)
else:
account.balance += request.amount
session.commit()
updated_balance = account.balance
metrics_registry.increment("rpc_mint_faucet_success_total")
metrics_registry.observe("rpc_mint_faucet_duration_seconds", time.perf_counter() - start)
return {"address": request.address, "balance": updated_balance}
class TransactionData(BaseModel):
tx_hash: str
sender: str
recipient: str
payload: Dict[str, Any] = Field(default_factory=dict)
class ReceiptData(BaseModel):
receipt_id: str
job_id: str
payload: Dict[str, Any] = Field(default_factory=dict)
miner_signature: Optional[str] = None
coordinator_attestations: List[str] = Field(default_factory=list)
minted_amount: int = 0
recorded_at: str
class BlockImportRequest(BaseModel):
height: int = Field(gt=0)
hash: str
parent_hash: str
proposer: str
timestamp: str
tx_count: int = Field(ge=0)
state_root: Optional[str] = None
transactions: List[TransactionData] = Field(default_factory=list)
receipts: List[ReceiptData] = Field(default_factory=list)
@router.get("/test-endpoint", summary="Test endpoint")
async def test_endpoint() -> Dict[str, str]:
"""Test if new code is deployed"""
return {"status": "updated_code_running"}
@router.post("/blocks/import", summary="Import block from remote node")
async def import_block(request: BlockImportRequest) -> Dict[str, Any]:
"""Import a block from a remote node after validation."""
import logging
logger = logging.getLogger(__name__)
metrics_registry.increment("rpc_import_block_total")
start = time.perf_counter()
try:
logger.info(f"Received block import request: height={request.height}, hash={request.hash}")
logger.info(f"Transactions count: {len(request.transactions)}")
if request.transactions:
logger.info(f"First transaction: {request.transactions[0]}")
with session_scope() as session:
# Check if block already exists
existing = session.exec(select(Block).where(Block.height == request.height)).first()
if existing:
if existing.hash == request.hash:
metrics_registry.increment("rpc_import_block_exists_total")
return {"status": "exists", "height": request.height, "hash": request.hash}
else:
metrics_registry.increment("rpc_import_block_conflict_total")
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=f"Block at height {request.height} already exists with different hash"
)
# Check if parent block exists
if request.height > 0:
parent = session.exec(select(Block).where(Block.hash == request.parent_hash)).first()
if not parent:
metrics_registry.increment("rpc_import_block_orphan_total")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Parent block not found"
)
# Validate block hash using the same algorithm as PoA proposer
payload = f"{settings.chain_id}|{request.height}|{request.parent_hash}|{request.timestamp}".encode()
expected_hash = "0x" + hashlib.sha256(payload).hexdigest()
if request.hash != expected_hash:
metrics_registry.increment("rpc_import_block_invalid_hash_total")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Invalid block hash. Expected: {expected_hash}, Got: {request.hash}"
)
# Create and save block
block_timestamp = datetime.fromisoformat(request.timestamp.replace('Z', '+00:00'))
block = Block(
height=request.height,
hash=request.hash,
parent_hash=request.parent_hash,
proposer=request.proposer,
timestamp=block_timestamp,
tx_count=request.tx_count,
state_root=request.state_root
)
session.add(block)
session.flush() # Get block ID
# Add transactions if provided
for tx_data in request.transactions:
# Create transaction using constructor with all fields
tx = Transaction(
tx_hash=str(tx_data.tx_hash),
block_height=block.id, # Use block.id instead of block.height for foreign key
sender=str(tx_data.sender),
recipient=str(tx_data.recipient),
payload=tx_data.payload if tx_data.payload else {},
created_at=datetime.utcnow()
)
session.add(tx)
# Add receipts if provided
for receipt_data in request.receipts:
receipt = Receipt(
block_height=block.id, # Use block.id instead of block.height for foreign key
receipt_id=receipt_data.receipt_id,
job_id=receipt_data.job_id,
payload=receipt_data.payload,
miner_signature=receipt_data.miner_signature,
coordinator_attestations=receipt_data.coordinator_attestations,
minted_amount=receipt_data.minted_amount,
recorded_at=datetime.fromisoformat(receipt_data.recorded_at.replace('Z', '+00:00'))
)
session.add(receipt)
session.commit()
# Broadcast block via gossip
try:
gossip_broker.broadcast("blocks", {
"type": "block_imported",
"height": block.height,
"hash": block.hash,
"proposer": block.proposer
})
except Exception:
pass # Gossip broadcast is optional
metrics_registry.increment("rpc_import_block_success_total")
metrics_registry.observe("rpc_import_block_duration_seconds", time.perf_counter() - start)
logger.info(f"Successfully imported block {request.height}")
return {
"status": "imported",
"height": block.height,
"hash": block.hash,
"tx_count": block.tx_count
}
except HTTPException:
# Re-raise HTTP exceptions as-is
raise
except Exception as e:
logger.error(f"Failed to import block {request.height}: {str(e)}", exc_info=True)
metrics_registry.increment("rpc_import_block_failed_total")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Internal server error: {str(e)}"
)

View File

@@ -1,7 +0,0 @@
"""
Cross-site synchronization module for AITBC blockchain.
"""
from .cross_site import CrossSiteSync
__all__ = ['CrossSiteSync']

View File

@@ -1,222 +0,0 @@
"""
Cross-site RPC synchronization module for AITBC blockchain nodes.
Enables block and transaction synchronization across different sites via HTTP RPC endpoints.
"""
import asyncio
import logging
from typing import List, Dict, Optional, Any
import httpx
from datetime import datetime, timedelta
logger = logging.getLogger(__name__)
class CrossSiteSync:
"""Handles synchronization with remote blockchain nodes via RPC."""
def __init__(self, local_rpc_url: str, remote_endpoints: List[str], poll_interval: int = 10):
"""
Initialize cross-site synchronization.
Args:
local_rpc_url: URL of local RPC endpoint (e.g., "http://localhost:8082")
remote_endpoints: List of remote RPC URLs to sync with
poll_interval: Seconds between sync checks
"""
self.local_rpc_url = local_rpc_url.rstrip('/')
self.remote_endpoints = remote_endpoints
self.poll_interval = poll_interval
self.last_sync = {}
self.sync_task = None
self.client = httpx.AsyncClient(timeout=5.0)
async def get_remote_head(self, endpoint: str) -> Optional[Dict[str, Any]]:
"""Get the head block from a remote node."""
try:
response = await self.client.get(f"{endpoint.rstrip('/')}/head")
if response.status_code == 200:
return response.json()
except Exception as e:
logger.error(f"Failed to get head from {endpoint}: {e}")
return None
async def get_remote_block(self, endpoint: str, height: int) -> Optional[Dict[str, Any]]:
"""Get a specific block from a remote node."""
try:
response = await self.client.get(f"{endpoint.rstrip('/')}/blocks/{height}")
if response.status_code == 200:
return response.json()
except Exception as e:
logger.error(f"Failed to get block {height} from {endpoint}: {e}")
return None
async def get_local_head(self) -> Optional[Dict[str, Any]]:
"""Get the local head block."""
try:
response = await self.client.get(f"{self.local_rpc_url}/rpc/head")
if response.status_code == 200:
return response.json()
except Exception as e:
logger.error(f"Failed to get local head: {e}")
return None
async def import_block(self, block_data: Dict[str, Any]) -> bool:
"""Import a block from a remote node."""
try:
response = await self.client.post(
f"{self.local_rpc_url}/rpc/blocks/import",
json=block_data
)
if response.status_code == 200:
result = response.json()
if result.get("status") in ["imported", "exists"]:
logger.info(f"Successfully imported block {block_data.get('height')}")
return True
else:
logger.error(f"Block import failed: {result}")
return False
else:
logger.error(f"Block import request failed: {response.status_code} {response.text}")
return False
except Exception as e:
logger.error(f"Failed to import block: {e}")
return False
async def submit_block(self, block_data: Dict[str, Any]) -> bool:
"""Submit a block to the local node."""
try:
response = await self.client.post(
f"{self.local_rpc_url}/rpc/block",
json=block_data
)
return response.status_code == 200
except Exception as e:
logger.error(f"Failed to submit block: {e}")
return False
async def sync_with_remotes(self) -> None:
"""Check and sync with all remote endpoints."""
local_head = await self.get_local_head()
if not local_head:
return
local_height = local_head.get('height', 0)
for endpoint in self.remote_endpoints:
remote_head = await self.get_remote_head(endpoint)
if not remote_head:
continue
remote_height = remote_head.get('height', 0)
# If remote is ahead, fetch missing blocks
if remote_height > local_height:
logger.info(f"Remote {endpoint} is ahead (height {remote_height} vs {local_height})")
# Fetch missing blocks one by one
for height in range(local_height + 1, remote_height + 1):
block = await self.get_remote_block(endpoint, height)
if block:
# Format block data for import
import_data = {
"height": block.get("height"),
"hash": block.get("hash"),
"parent_hash": block.get("parent_hash"),
"proposer": block.get("proposer"),
"timestamp": block.get("timestamp"),
"tx_count": block.get("tx_count", 0),
"state_root": block.get("state_root"),
"transactions": block.get("transactions", []),
"receipts": block.get("receipts", [])
}
success = await self.import_block(import_data)
if success:
logger.info(f"Imported block {height} from {endpoint}")
local_height = height
else:
logger.error(f"Failed to import block {height}")
break
else:
logger.error(f"Failed to fetch block {height} from {endpoint}")
break
async def get_remote_mempool(self, endpoint: str) -> List[Dict[str, Any]]:
"""Get mempool transactions from a remote node."""
try:
response = await self.client.get(f"{endpoint.rstrip('/')}/mempool")
if response.status_code == 200:
return response.json()
except Exception as e:
logger.error(f"Failed to get mempool from {endpoint}: {e}")
return []
async def get_local_mempool(self) -> List[Dict[str, Any]]:
"""Get local mempool transactions."""
try:
response = await self.client.get(f"{self.local_rpc_url}/rpc/mempool")
if response.status_code == 200:
return response.json()
except Exception as e:
logger.error(f"Failed to get local mempool: {e}")
return []
async def submit_transaction(self, tx_data: Dict[str, Any]) -> bool:
"""Submit a transaction to the local node."""
try:
response = await self.client.post(
f"{self.local_rpc_url}/rpc/transaction",
json=tx_data
)
return response.status_code == 200
except Exception as e:
logger.error(f"Failed to submit transaction: {e}")
return False
async def sync_transactions(self) -> None:
"""Sync transactions from remote mempools."""
local_mempool = await self.get_local_mempool()
local_tx_hashes = {tx.get('hash') for tx in local_mempool}
for endpoint in self.remote_endpoints:
remote_mempool = await self.get_remote_mempool(endpoint)
for tx in remote_mempool:
tx_hash = tx.get('hash')
if tx_hash and tx_hash not in local_tx_hashes:
success = await self.submit_transaction(tx)
if success:
logger.info(f"Imported transaction {tx_hash[:8]}... from {endpoint}")
async def sync_loop(self) -> None:
"""Main synchronization loop."""
logger.info("Starting cross-site sync loop")
while True:
try:
# Sync blocks
await self.sync_with_remotes()
# Sync transactions
await self.sync_transactions()
except Exception as e:
logger.error(f"Error in sync loop: {e}")
await asyncio.sleep(self.poll_interval)
async def start(self) -> None:
"""Start the synchronization task."""
if self.sync_task is None:
self.sync_task = asyncio.create_task(self.sync_loop())
async def stop(self) -> None:
"""Stop the synchronization task."""
if self.sync_task:
self.sync_task.cancel()
try:
await self.sync_task
except asyncio.CancelledError:
pass
self.sync_task = None
await self.client.aclose()

10
website/favicon.svg Normal file
View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<defs>
<linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#6366f1"/>
<stop offset="100%" style="stop-color:#8b5cf6"/>
</linearGradient>
</defs>
<rect width="64" height="64" rx="14" fill="url(#g)"/>
<text x="32" y="44" font-family="system-ui,sans-serif" font-size="32" font-weight="700" fill="white" text-anchor="middle">AI</text>
</svg>

After

Width:  |  Height:  |  Size: 471 B