Automated maintenance update - Mo 30 Mär 2026 07:52:40 CEST
All checks were successful
CLI Tests / test-cli (push) Successful in 1m30s
Documentation Validation / validate-docs (push) Successful in 26s
Integration Tests / test-service-integration (push) Successful in 1m0s
Python Tests / test-python (push) Successful in 1m16s
Security Scanning / security-scan (push) Successful in 1m3s

This commit is contained in:
2026-03-30 07:52:40 +02:00
parent 430120e94c
commit 5775b51969
9 changed files with 1942 additions and 87 deletions

View File

@@ -4,7 +4,7 @@ description: Multi-node blockchain deployment and setup workflow
# Multi-Node Blockchain Deployment Workflow # Multi-Node Blockchain Deployment Workflow
This workflow sets up a two-node AITBC blockchain network (aitbc1 as genesis authority, aitbc as follower node), creates wallets, and demonstrates cross-node transactions. This workflow sets up a two-node AITBC blockchain network (aitbc as genesis authority/primary development server, aitbc1 as follower node), creates wallets, and demonstrates cross-node transactions.
## Prerequisites ## Prerequisites
@@ -48,36 +48,36 @@ The workflow uses the single central `/etc/aitbc/.env` file as the configuration
### 🚨 Important: Genesis Block Architecture ### 🚨 Important: Genesis Block Architecture
**CRITICAL**: Only the genesis authority node (aitbc1) should have the genesis block! **CRITICAL**: Only the genesis authority node (aitbc) should have the genesis block!
```bash ```bash
# ❌ WRONG - Do NOT copy genesis block to follower nodes # ❌ WRONG - Do NOT copy genesis block to follower nodes
# scp aitbc1:/var/lib/aitbc/data/ait-mainnet/genesis.json aitbc:/var/lib/aitbc/data/ait-mainnet/ # scp aitbc:/var/lib/aitbc/data/ait-mainnet/genesis.json aitbc1:/var/lib/aitbc/data/ait-mainnet/
# ✅ CORRECT - Follower nodes sync genesis via blockchain protocol # ✅ CORRECT - Follower nodes sync genesis via blockchain protocol
# aitbc will automatically receive genesis block from aitbc1 during sync # aitbc1 will automatically receive genesis block from aitbc during sync
``` ```
**Architecture Overview:** **Architecture Overview:**
1. **aitbc1 (Genesis Authority)**: Creates genesis block with initial wallets 1. **aitbc (Genesis Authority/Primary Development Server)**: Creates genesis block with initial wallets
2. **aitbc (Follower Node)**: Syncs from aitbc1, receives genesis block automatically 2. **aitbc1 (Follower Node)**: Syncs from aitbc, receives genesis block automatically
3. **Wallet Creation**: New wallets attach to existing blockchain using genesis keys 3. **Wallet Creation**: New wallets attach to existing blockchain using genesis keys
4. **Access AIT Coins**: Genesis wallets control initial supply, new wallets receive via transactions 4. **Access AIT Coins**: Genesis wallets control initial supply, new wallets receive via transactions
**Key Principles:** **Key Principles:**
- **Single Genesis Source**: Only aitbc1 creates and holds the original genesis block - **Single Genesis Source**: Only aitbc creates and holds the original genesis block
- **Blockchain Sync**: Followers receive blockchain data through sync protocol, not file copying - **Blockchain Sync**: Followers receive blockchain data through sync protocol, not file copying
- **Wallet Attachment**: New wallets attach to existing chain, don't create new genesis - **Wallet Attachment**: New wallets attach to existing chain, don't create new genesis
- **Coin Access**: AIT coins are accessed through transactions from genesis wallets - **Coin Access**: AIT coins are accessed through transactions from genesis wallets
### 1. Prepare aitbc1 (Genesis Authority Node) ### 1. Prepare aitbc (Genesis Authority/Primary Development Server)
```bash ```bash
# Run the genesis authority setup script # Run the genesis authority setup script
/opt/aitbc/scripts/workflow/02_genesis_authority_setup.sh /opt/aitbc/scripts/workflow/02_genesis_authority_setup.sh
``` ```
### 2. Verify aitbc1 Genesis State ### 2. Verify aitbc Genesis State
```bash ```bash
# Check blockchain state # Check blockchain state
@@ -86,15 +86,15 @@ curl -s http://localhost:8006/rpc/info | jq .
curl -s http://localhost:8006/rpc/supply | jq . curl -s http://localhost:8006/rpc/supply | jq .
# Check genesis wallet balance # Check genesis wallet balance
GENESIS_ADDR=$(cat /var/lib/aitbc/keystore/aitbc1genesis.json | jq -r '.address') GENESIS_ADDR=$(cat /var/lib/aitbc/keystore/aitbcgenesis.json | jq -r '.address')
curl -s "http://localhost:8006/rpc/getBalance/$GENESIS_ADDR" | jq . curl -s "http://localhost:8006/rpc/getBalance/$GENESIS_ADDR" | jq .
``` ```
### 3. Prepare aitbc (Follower Node) ### 3. Prepare aitbc1 (Follower Node)
```bash ```bash
# Run the follower node setup script (executed on aitbc) # Run the follower node setup script (executed on aitbc1)
ssh aitbc '/opt/aitbc/scripts/workflow/03_follower_node_setup.sh' ssh aitbc1 '/opt/aitbc/scripts/workflow/03_follower_node_setup.sh'
``` ```
### 4. Watch Blockchain Sync ### 4. Watch Blockchain Sync
@@ -108,13 +108,14 @@ ssh aitbc1 'curl -s http://localhost:8006/rpc/head | jq .height'
# Alternative: Batch sync for faster initial setup # Alternative: Batch sync for faster initial setup
if [ $(curl -s http://localhost:8006/rpc/head | jq .height) -lt 10 ]; then if [ $(curl -s http://localhost:8006/rpc/head | jq .height) -lt 10 ]; then
echo "Importing first 10 blocks from aitbc1..." echo "Importing first 10 blocks from aitbc to aitbc1..."
for height in {2..10}; do for height in {2..10}; do
curl -s "http://10.1.223.40:8006/rpc/blocks-range?start=$height&end=$height" | \ curl -s "http://localhost:8006/rpc/blocks-range?start=$height&end=$height" | \
jq '.blocks[0]' > /tmp/block$height.json jq '.blocks[0]' > /tmp/block$height.json
curl -X POST http://localhost:8006/rpc/importBlock \ scp /tmp/block$height.json aitbc1:/tmp/
-H "Content-Type: application/json" -d @/tmp/block$height.json ssh aitbc1 "curl -X POST http://localhost:8006/rpc/importBlock \
echo "Imported block $height" -H \"Content-Type: application/json\" -d @/tmp/block$height.json"
echo "Imported block $height to aitbc1"
done done
fi fi
``` ```
@@ -129,7 +130,7 @@ fi
**🔑 Wallet Attachment & Coin Access:** **🔑 Wallet Attachment & Coin Access:**
The newly created wallet on aitbc will: The newly created wallet on aitbc will:
1. **Attach to Existing Blockchain**: Connect to the blockchain created by aitbc1 1. **Attach to Existing Blockchain**: Connect to the blockchain created by aitbc (genesis authority)
2. **Use Genesis Keys**: Access the blockchain using the genesis block's cryptographic keys 2. **Use Genesis Keys**: Access the blockchain using the genesis block's cryptographic keys
3. **Receive AIT Coins**: Get coins through transactions from genesis wallets 3. **Receive AIT Coins**: Get coins through transactions from genesis wallets
4. **No New Genesis**: Does NOT create a new genesis block or chain 4. **No New Genesis**: Does NOT create a new genesis block or chain
@@ -138,7 +139,7 @@ The newly created wallet on aitbc will:
- The wallet attaches to the existing blockchain network - The wallet attaches to the existing blockchain network
- AIT coins are transferred from genesis wallets, not created - AIT coins are transferred from genesis wallets, not created
- The wallet can only transact after receiving coins from genesis - The wallet can only transact after receiving coins from genesis
- All wallets share the same blockchain, created by aitbc1 - All wallets share the same blockchain, created by aitbc (primary development server)
### 6. Blockchain Sync Fix (Enhanced) ### 6. Blockchain Sync Fix (Enhanced)

3
aitbc-cli Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
source /opt/aitbc/cli/venv/bin/activate
python /opt/aitbc/cli/simple_wallet.py "$@"

View File

@@ -0,0 +1,59 @@
#!/usr/bin/env python3
"""Load genesis accounts into the blockchain database"""
import json
import sys
from pathlib import Path
# Add the src directory to the path
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
from aitbc_chain.database import session_scope
from aitbc_chain.models import Account
from aitbc_chain.config import settings
def load_genesis_accounts(genesis_path: str = "data/devnet/genesis.json"):
"""Load accounts from genesis file into database"""
# Read genesis file
genesis_file = Path(genesis_path)
if not genesis_file.exists():
print(f"Error: Genesis file not found at {genesis_path}")
return False
with open(genesis_file) as f:
genesis = json.load(f)
chain_id = genesis.get("chain_id", settings.chain_id)
# Load accounts
with session_scope() as session:
for account_data in genesis.get("allocations", []):
address = account_data["address"]
balance = account_data["balance"]
nonce = account_data.get("nonce", 0)
# Check if account already exists
existing = session.query(Account).filter_by(chain_id=chain_id, address=address).first()
if existing:
existing.balance = balance
existing.nonce = nonce
print(f"Updated account {address}: balance={balance}")
else:
account = Account(chain_id=chain_id, address=address, balance=balance, nonce=nonce)
session.add(account)
print(f"Created account {address}: balance={balance}")
session.commit()
print("\\nGenesis accounts loaded successfully!")
return True
if __name__ == "__main__":
if len(sys.argv) > 1:
genesis_path = sys.argv[1]
else:
genesis_path = "data/devnet/genesis.json"
success = load_genesis_accounts(genesis_path)
sys.exit(0 if success else 1)

View File

@@ -17,11 +17,24 @@ from ..mempool import get_mempool
from ..metrics import metrics_registry from ..metrics import metrics_registry
from ..models import Account, Block, Receipt, Transaction from ..models import Account, Block, Receipt, Transaction
from ..logger import get_logger from ..logger import get_logger
from ..contracts.agent_messaging_contract import messaging_contract
_logger = get_logger(__name__) _logger = get_logger(__name__)
router = APIRouter() router = APIRouter()
# Global variable to store the PoA proposer
_poa_proposer = None
def set_poa_proposer(proposer):
"""Set the global PoA proposer instance"""
global _poa_proposer
_poa_proposer = proposer
def get_poa_proposer():
"""Get the global PoA proposer instance"""
return _poa_proposer
def get_chain_id(chain_id: str = None) -> str: def get_chain_id(chain_id: str = None) -> str:
"""Get chain_id from parameter or use default from settings""" """Get chain_id from parameter or use default from settings"""
if chain_id is None: if chain_id is None:
@@ -130,6 +143,87 @@ async def get_block(height: int) -> Dict[str, Any]:
} }
@router.post("/transaction", summary="Submit transaction")
async def submit_transaction(tx_data: dict) -> Dict[str, Any]:
"""Submit a new transaction to the mempool"""
from ..mempool import get_mempool
from ..models import Transaction
try:
mempool = get_mempool()
# Create transaction data as dictionary
tx_data_dict = {
"chain_id": tx_data.get("chain_id", "ait-mainnet"),
"from": tx_data["from"],
"to": tx_data["to"],
"amount": tx_data["amount"],
"fee": tx_data.get("fee", 10),
"nonce": tx_data.get("nonce", 0),
"payload": tx_data.get("payload", "0x"),
"signature": tx_data.get("signature")
}
# Add to mempool
tx_hash = mempool.add(tx_data_dict)
return {
"success": True,
"transaction_hash": tx_hash,
"message": "Transaction submitted to mempool"
}
except Exception as e:
_logger.error("Failed to submit transaction", extra={"error": str(e)})
raise HTTPException(status_code=400, detail=f"Failed to submit transaction: {str(e)}")
@router.get("/mempool", summary="Get pending transactions")
async def get_mempool(chain_id: str = None, limit: int = 100) -> Dict[str, Any]:
"""Get pending transactions from mempool"""
from ..mempool import get_mempool
try:
mempool = get_mempool()
pending_txs = mempool.get_pending_transactions(chain_id=chain_id, limit=limit)
return {
"success": True,
"transactions": pending_txs,
"count": len(pending_txs)
}
except Exception as e:
_logger.error("Failed to get mempool", extra={"error": str(e)})
raise HTTPException(status_code=500, detail=f"Failed to get mempool: {str(e)}")
@router.get("/accounts/{address}", summary="Get account information")
async def get_account(address: str) -> Dict[str, Any]:
"""Get account information including balance"""
from ..models import Account
try:
with session_scope() as session:
account = session.exec(select(Account).where(Account.address == address)).first()
if account is None:
return {
"address": address,
"balance": 0,
"nonce": 0,
"exists": False
}
return {
"address": account.address,
"balance": account.balance,
"nonce": account.nonce,
"exists": True
}
except Exception as e:
_logger.error("Failed to get account", extra={"error": str(e), "address": address})
raise HTTPException(status_code=500, detail=f"Failed to get account: {str(e)}")
@router.get("/blocks-range", summary="Get blocks in height range") @router.get("/blocks-range", summary="Get blocks in height range")
# Working contract endpoints # Working contract endpoints
async def get_messaging_contract_state() -> Dict[str, Any]: async def get_messaging_contract_state() -> Dict[str, Any]:
@@ -213,8 +307,7 @@ async def moderate_message(message_id: str, moderation_data: dict) -> Dict[str,
moderation_data.get("action"), moderation_data.get("action"),
moderation_data.get("reason", "") moderation_data.get("reason", "")
) )
EOF && echo "✅ Messaging contract endpoints added" && echo -e "\n2. Restarting blockchain node to load new endpoints:" && systemctl restart aitbc-blockchain-node && sleep 5 && echo "✅ Blockchain node restarted" && echo -e "\n3. Testing messaging endpoints:" && echo " Testing forum topics endpoint:" && curl -s http://localhost:8006/rpc/messaging/topics | jq .total_topics && echo -e "\n Testing message search endpoint:" && curl -s "http://localhost:8006/rpc/messaging/messages/search?query=test" | jq .total_matches
from .contracts.agent_messaging_contract import messaging_contract
@router.get("/rpc/messaging/topics", summary="Get forum topics") @router.get("/rpc/messaging/topics", summary="Get forum topics")
async def get_forum_topics(limit: int = 50, offset: int = 0, sort_by: str = "last_activity") -> Dict[str, Any]: async def get_forum_topics(limit: int = 50, offset: int = 0, sort_by: str = "last_activity") -> Dict[str, Any]:
"""Get list of forum topics""" """Get list of forum topics"""
@@ -270,9 +363,6 @@ async def post_message(message_data: dict) -> Dict[str, Any]:
message_data.get("message_type", "post"), message_data.get("message_type", "post"),
message_data.get("parent_message_id") message_data.get("parent_message_id")
) )
EOF && echo "✅ Added working endpoints" && echo -e "\n6. Restarting node with fixed configuration:" && systemctl restart aitbc-blockchain-node && sleep 5 && echo "✅ Node restarted" && echo -e "\n7. Testing fixed endpoints:" && curl -s http://localhost:8006/rpc/messaging/topics | jq .success && echo -e "\n8. Creating test topic:" && curl -s -X POST http://localhost:8006/rpc/messaging/topics/create \
-H "Content-Type: application/json" \
-d '{"agent_id": "test_agent", "agent_address": "ait1test_agent", "title": "Fixed Test Topic", "description": "Testing after fixes", "tags": ["test"]}' | jq .topic_id
# Agent messaging contract endpoints # Agent messaging contract endpoints
@router.get("/rpc/messaging/topics", summary="Get forum topics") @router.get("/rpc/messaging/topics", summary="Get forum topics")
@@ -302,6 +392,3 @@ async def post_message(message_data: dict) -> Dict[str, Any]:
message_data.get("message_type", "post"), message_data.get("message_type", "post"),
message_data.get("parent_message_id") message_data.get("parent_message_id")
) )
EOF && echo "✅ Added clean endpoints" && echo -e "\n5. Restarting node:" && systemctl restart aitbc-blockchain-node && sleep 5 && echo "✅ Node restarted" && echo -e "\n6. Testing messaging contract:" && curl -s http://localhost:8006/rpc/messaging/topics | jq .success && echo -e "\n7. Creating test topic:" && curl -s -X POST http://localhost:8006/rpc/messaging/topics/create \
-H "Content-Type: application/json" \
-d '{"agent_id": "test_agent", "agent_address": "ait1test_agent", "title": "Working Test Topic", "description": "Testing after syntax fix", "tags": ["test"]}' | jq .topic_id

88
cli/requirements.txt Normal file
View File

@@ -0,0 +1,88 @@
# AITBC Central Virtual Environment Requirements
# This file contains all Python dependencies for AITBC services
# Merged from all subdirectory requirements files
# Core Web Framework
fastapi>=0.115.0
uvicorn[standard]>=0.32.0
gunicorn>=22.0.0
# Database & ORM
sqlalchemy>=2.0.0
sqlalchemy[asyncio]>=2.0.47
sqlmodel>=0.0.37
alembic>=1.18.0
aiosqlite>=0.20.0
asyncpg>=0.29.0
# Configuration & Environment
pydantic>=2.12.0
pydantic-settings>=2.13.0
python-dotenv>=1.2.0
# Rate Limiting & Security
slowapi>=0.1.9
limits>=5.8.0
prometheus-client>=0.24.0
# HTTP Client & Networking
httpx>=0.28.0
requests>=2.32.0
aiohttp>=3.9.0
# Cryptocurrency & Blockchain
cryptography>=46.0.0
pynacl>=1.5.0
ecdsa>=0.19.0
base58>=2.1.1
web3>=6.11.0
eth-account>=0.13.0
# Data Processing
pandas>=2.2.0
numpy>=1.26.0
# Development & Testing
pytest>=8.0.0
pytest-asyncio>=0.24.0
black>=24.0.0
flake8>=7.0.0
# CLI Tools
click>=8.1.0
rich>=13.0.0
typer>=0.12.0
click-completion>=0.5.2
tabulate>=0.9.0
colorama>=0.4.4
keyring>=23.0.0
# JSON & Serialization
orjson>=3.10.0
msgpack>=1.1.0
python-multipart>=0.0.6
# Logging & Monitoring
structlog>=24.1.0
sentry-sdk>=2.0.0
# Utilities
python-dateutil>=2.9.0
pytz>=2024.1
schedule>=1.2.0
aiofiles>=24.1.0
pyyaml>=6.0
# Async Support
asyncio-mqtt>=0.16.0
websockets>=13.0.0
# Image Processing (for AI services)
pillow>=10.0.0
opencv-python>=4.9.0
# Additional Dependencies
redis>=5.0.0
psutil>=5.9.0
tenseal
web3>=6.11.0

View File

@@ -115,13 +115,13 @@ def send_transaction(from_wallet: str, to_address: str, amount: float, fee: floa
# Create transaction # Create transaction
transaction = { transaction = {
"sender": sender_address, "from": sender_address,
"recipient": to_address, "to": to_address,
"value": int(amount), "amount": int(amount),
"fee": int(fee), "fee": int(fee),
"nonce": 0, # In real implementation, get current nonce "nonce": 0, # In real implementation, get current nonce
"type": "transfer", "payload": "0x",
"payload": {} "chain_id": "ait-mainnet"
} }
# Sign transaction (simplified) # Sign transaction (simplified)
@@ -131,7 +131,7 @@ def send_transaction(from_wallet: str, to_address: str, amount: float, fee: floa
# Submit transaction # Submit transaction
try: try:
response = requests.post(f"{rpc_url}/sendTx", json=transaction) response = requests.post(f"{rpc_url}/rpc/transaction", json=transaction)
if response.status_code == 200: if response.status_code == 200:
result = response.json() result = response.json()
print(f"Transaction submitted successfully") print(f"Transaction submitted successfully")
@@ -596,10 +596,42 @@ def get_transactions(wallet_name: str, keystore_dir: Path = DEFAULT_KEYSTORE_DIR
return [] return []
def get_balance(wallet_name: str, rpc_url: str = DEFAULT_RPC_URL) -> Optional[Dict]:
"""Get wallet balance"""
try:
# Get wallet address
wallet_path = DEFAULT_KEYSTORE_DIR / f"{wallet_name}.json"
if not wallet_path.exists():
print(f"Wallet {wallet_name} not found")
return None
with open(wallet_path) as f:
wallet_data = json.load(f)
address = wallet_data["address"]
# Get account info from RPC
response = requests.get(f"{rpc_url}/rpc/accounts/{address}")
if response.status_code == 200:
account_info = response.json()
return {
"wallet_name": wallet_name,
"address": address,
"balance": account_info["balance"],
"nonce": account_info["nonce"]
}
else:
print(f"Error getting balance: {response.text}")
return None
except Exception as e:
print(f"Error: {e}")
return None
def get_chain_info(rpc_url: str = DEFAULT_RPC_URL) -> Optional[Dict]: def get_chain_info(rpc_url: str = DEFAULT_RPC_URL) -> Optional[Dict]:
"""Get blockchain information""" """Get blockchain information"""
try: try:
response = requests.get(f"{rpc_url}/rpc/info") # Use the head endpoint to get chain info
response = requests.get(f"{rpc_url}/rpc/head")
if response.status_code == 200: if response.status_code == 200:
return response.json() return response.json()
else: else:

1148
config/python/poetry.lock generated

File diff suppressed because it is too large Load Diff

88
requirements.txt Normal file
View File

@@ -0,0 +1,88 @@
# AITBC Central Virtual Environment Requirements
# This file contains all Python dependencies for AITBC services
# Merged from all subdirectory requirements files
# Core Web Framework
fastapi>=0.115.0
uvicorn[standard]>=0.32.0
gunicorn>=22.0.0
# Database & ORM
sqlalchemy>=2.0.0
sqlalchemy[asyncio]>=2.0.47
sqlmodel>=0.0.37
alembic>=1.18.0
aiosqlite>=0.20.0
asyncpg>=0.29.0
# Configuration & Environment
pydantic>=2.12.0
pydantic-settings>=2.13.0
python-dotenv>=1.2.0
# Rate Limiting & Security
slowapi>=0.1.9
limits>=5.8.0
prometheus-client>=0.24.0
# HTTP Client & Networking
httpx>=0.28.0
requests>=2.32.0
aiohttp>=3.9.0
# Cryptocurrency & Blockchain
cryptography>=46.0.0
pynacl>=1.5.0
ecdsa>=0.19.0
base58>=2.1.1
web3>=6.11.0
eth-account>=0.13.0
# Data Processing
pandas>=2.2.0
numpy>=1.26.0
# Development & Testing
pytest>=8.0.0
pytest-asyncio>=0.24.0
black>=24.0.0
flake8>=7.0.0
# CLI Tools
click>=8.1.0
rich>=13.0.0
typer>=0.12.0
click-completion>=0.5.2
tabulate>=0.9.0
colorama>=0.4.4
keyring>=23.0.0
# JSON & Serialization
orjson>=3.10.0
msgpack>=1.1.0
python-multipart>=0.0.6
# Logging & Monitoring
structlog>=24.1.0
sentry-sdk>=2.0.0
# Utilities
python-dateutil>=2.9.0
pytz>=2024.1
schedule>=1.2.0
aiofiles>=24.1.0
pyyaml>=6.0
# Async Support
asyncio-mqtt>=0.16.0
websockets>=13.0.0
# Image Processing (for AI services)
pillow>=10.0.0
opencv-python>=4.9.0
# Additional Dependencies
redis>=5.0.0
psutil>=5.9.0
tenseal
web3>=6.11.0

View File

@@ -0,0 +1,453 @@
#!/usr/bin/env python3
"""
Comprehensive End-to-End Test for AITBC Blockchain System
Tests: Node Sync, Transaction Flow, Block Creation, State Consistency
Fixed to use correct RPC endpoints based on actual API
"""
import requests
import json
import time
import sys
from typing import Dict, List, Optional
from datetime import datetime
class AITCBE2ETest:
def __init__(self):
self.rpc_url = "http://localhost:8006/rpc" # Fixed: Added /rpc prefix
self.test_results = []
self.start_time = time.time()
def log_test(self, test_name: str, status: str, details: str = "", duration: float = 0):
"""Log test result"""
result = {
"test": test_name,
"status": status, # PASS, FAIL, SKIP, INFO
"details": details,
"duration": round(duration, 3),
"timestamp": datetime.now().isoformat()
}
self.test_results.append(result)
status_icons = {
"PASS": "",
"FAIL": "",
"SKIP": "⏭️",
"INFO": ""
}
icon = status_icons.get(status, "")
print(f"{icon} [{duration:.3f}s] {test_name}")
if details:
print(f" {details}")
def make_rpc_call(self, method: str, params: dict = None) -> Optional[Dict]:
"""Make REST API call to blockchain node based on actual API endpoints"""
if params is None:
params = {}
# Map method names to actual endpoints based on OpenAPI spec
endpoint_map = {
"getInfo": "/info",
"getTransactions": "/transactions",
"getBlockByHeight": "/blocks/{height}",
"getTransactionByHash": "/tx/{tx_hash}",
"getBalance": "/getBalance/{address}",
"getAddressDetails": "/address/{address}",
"getBlockCount": "/blocks/count",
"getSyncStatus": "/syncStatus",
"getTokenSupply": "/supply",
"getValidators": "/validators",
"getChainState": "/state",
"sendTransaction": "/sendTx",
"submitReceipt": "/submitReceipt",
"estimateFee": "/estimateFee",
"importBlock": "/importBlock",
"getHead": "/head",
"getReceipts": "/receipts",
"getAddresses": "/addresses",
"health": "/health",
"metrics": "/metrics"
}
endpoint = endpoint_map.get(method, f"/{method}")
# Handle path parameters
if "{height}" in endpoint and params.get("height") is not None:
endpoint = endpoint.replace("{height}", str(params["height"]))
elif "{tx_hash}" in endpoint and params.get("tx_hash") is not None:
endpoint = endpoint.replace("{tx_hash}", params["tx_hash"])
elif "{address}" in endpoint and params.get("address") is not None:
endpoint = endpoint.replace("{address}", params["address"])
elif "{receipt_id}" in endpoint and params.get("receipt_id") is not None:
endpoint = endpoint.replace("{receipt_id}", params["receipt_id"])
# Remove used params
params = {k: v for k, v in params.items()
if k not in ["height", "tx_hash", "address", "receipt_id"]}
try:
# For GET requests with query parameters
response = requests.get(
f"{self.rpc_url}{endpoint}",
params=params,
timeout=10
)
response.raise_for_status()
return response.json()
except Exception as e:
return {"error": str(e)}
def test_node_connectivity(self) -> bool:
"""Test if blockchain nodes are reachable"""
start = time.time()
try:
# Test info endpoint
result = self.make_rpc_call("getInfo")
if result and "error" not in result:
self.log_test(
"Node RPC Connectivity",
"PASS",
f"Node responding at {self.rpc_url}",
time.time() - start
)
return True
else:
error_msg = result.get("error", "Unknown error") if result else "No response"
self.log_test(
"Node RPC Connectivity",
"FAIL",
f"RPC call failed: {error_msg}",
time.time() - start
)
return False
except Exception as e:
self.log_test(
"Node RPC Connectivity",
"FAIL",
f"Connection error: {str(e)}",
time.time() - start
)
return False
def test_chain_sync_status(self) -> bool:
"""Test blockchain synchronization status"""
start = time.time()
try:
result = self.make_rpc_call("getInfo")
if result and "error" not in result:
height = result.get("height", 0)
chain_id = result.get("chainId", "unknown")
details = f"Height: {height}, ChainID: {chain_id}"
# Check if we have reasonable block height (not necessarily > 0 in test env)
self.log_test(
"Chain Synchronization Status",
"PASS",
details,
time.time() - start
)
return True
else:
error_msg = result.get("error", "Unknown error") if result else "No response"
self.log_test(
"Chain Synchronization Status",
"FAIL",
f"Failed to get chain info: {error_msg}",
time.time() - start
)
return False
except Exception as e:
self.log_test(
"Chain Synchronization Status",
"FAIL",
f"Error checking sync status: {str(e)}",
time.time() - start
)
return False
def test_transaction_status(self) -> bool:
"""Test transaction status endpoint (replaces mempool check)"""
start = time.time()
try:
result = self.make_rpc_call("getTransactions")
if result and "error" not in result:
# Transactions endpoint returns latest transactions
tx_count = 0
if isinstance(result, dict) and "transactions" in result:
tx_count = len(result.get("transactions", []))
elif isinstance(result, list):
tx_count = len(result)
self.log_test(
"Transaction Status Check",
"PASS",
f"Recent transactions check successful ({tx_count} transactions)",
time.time() - start
)
return True
else:
error_msg = result.get("error", "Unknown error") if result else "No response"
self.log_test(
"Transaction Status Check",
"FAIL",
f"Transaction check failed: {error_msg}",
time.time() - start
)
return False
except Exception as e:
self.log_test(
"Transaction Status Check",
"FAIL",
f"Error checking transactions: {str(e)}",
time.time() - start
)
return False
def test_block_retrieval(self) -> bool:
"""Test retrieving recent blocks"""
start = time.time()
try:
# Get current height from info
info_result = self.make_rpc_call("getInfo")
if info_result and "error" not in info_result:
current_height = info_result.get("height", 0)
# Try to get a specific block if we have height > 0
if current_height > 0:
block_result = self.make_rpc_call("getBlockByHeight", {"height": current_height})
if block_result and "error" not in block_result:
tx_count = len(block_result.get("transactions", [])) if isinstance(block_result.get("transactions"), list) else 0
self.log_test(
"Block Retrieval Test",
"PASS",
f"Retrieved block {current_height} with {tx_count} transactions",
time.time() - start
)
return True
else:
error_msg = block_result.get("error", "Unknown error") if block_result else "No response"
self.log_test(
"Block Retrieval Test",
"FAIL",
f"Block retrieval failed: {error_msg}",
time.time() - start
)
return False
else:
self.log_test(
"Block Retrieval Test",
"PASS",
"Chain at height 0 (genesis) - basic functionality verified",
time.time() - start
)
return True
else:
error_msg = info_result.get("error", "Unknown error") if info_result else "No response"
self.log_test(
"Block Retrieval Test",
"FAIL",
f"Failed to get chain info: {error_msg}",
time.time() - start
)
return False
except Exception as e:
self.log_test(
"Block Retrieval Test",
"FAIL",
f"Error retrieving block: {str(e)}",
time.time() - start
)
return False
def test_transaction_system(self) -> bool:
"""Test transaction system availability"""
start = time.time()
try:
# Test if we can at least get balance info (basic transaction system validation)
result = self.make_rpc_call("getBalance", {"address": "ait1test0000000000000000000000000000000"})
if result and "error" not in result:
balance = result.get("balance", 0)
self.log_test(
"Transaction System Validation",
"PASS",
f"Balance query successful (balance: {balance})",
time.time() - start
)
return True
else:
error_msg = result.get("error", "Unknown error") if result else "No response"
self.log_test(
"Transaction System Validation",
"FAIL",
f"Transaction system not ready: {error_msg}",
time.time() - start
)
return False
except Exception as e:
self.log_test(
"Transaction System Validation",
"FAIL",
f"Error validating transaction system: {str(e)}",
time.time() - start
)
return False
def test_network_info(self) -> bool:
"""Test network information availability"""
start = time.time()
try:
result = self.make_rpc_call("getInfo")
if result and "error" not in result:
chain_id = result.get("chainId", "unknown")
version = result.get("rpcVersion", "unknown")
details = f"ChainID: {chain_id}, RPC Version: {version}"
self.log_test(
"Network Information",
"PASS",
details,
time.time() - start
)
return True
else:
error_msg = result.get("error", "Unknown error") if result else "No response"
self.log_test(
"Network Information",
"FAIL",
f"Failed to get network info: {error_msg}",
time.time() - start
)
return False
except Exception as e:
self.log_test(
"Network Information",
"FAIL",
f"Error checking network info: {str(e)}",
time.time() - start
)
return False
def run_all_tests(self) -> Dict:
"""Run all E2E tests and return summary"""
print("🧪 AITBC Blockchain Comprehensive End-to-End Test Suite")
print("=" * 70)
print(f"🕐 Started at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"🎯 Testing RPC endpoint: {self.rpc_url}")
print()
# Run all tests
tests = [
self.test_node_connectivity,
self.test_chain_sync_status,
self.test_transaction_status,
self.test_block_retrieval,
self.test_transaction_system,
self.test_network_info
]
for test in tests:
try:
test()
except Exception as e:
print(f"💥 Test {test.__name__} crashed: {str(e)}")
self.log_test(test.__name__, "FAIL", f"Test crashed: {str(e)}")
# Generate summary
return self.generate_test_summary()
def generate_test_summary(self) -> Dict:
"""Generate test summary report"""
end_time = time.time()
total_duration = end_time - self.start_time
passed = sum(1 for t in self.test_results if t["status"] == "PASS")
failed = sum(1 for t in self.test_results if t["status"] == "FAIL")
skipped = sum(1 for t in self.test_results if t["status"] == "SKIP")
total = len(self.test_results)
success_rate = (passed / total * 100) if total > 0 else 0
print("\n" + "=" * 70)
print("📊 END-TO-END TEST SUMMARY")
print("=" * 70)
print(f"🕐 Completed at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"⏱️ Total Duration: {total_duration:.2f} seconds")
print(f"📈 Results: {passed} PASS, {failed} FAIL, {skipped} SKIP (Total: {total})")
print(f"🎯 Success Rate: {success_rate:.1f}%")
print(f"\n📋 Detailed Results:")
for result in self.test_results:
status_icon = {
"PASS": "",
"FAIL": "",
"SKIP": "⏭️",
"INFO": ""
}.get(result["status"], "")
print(f" {status_icon} {result['test']} [{result['duration']}s]")
if result["details"] and result["status"] != "PASS":
print(f"{result['details']}")
# Overall assessment
if failed == 0:
print(f"\n🎉 OVERALL STATUS: ALL TESTS PASSED")
print(f"✅ The AITBC blockchain system is functioning correctly!")
elif passed >= total * 0.6: # 60% pass rate for more realistic assessment
print(f"\n⚠️ OVERALL STATUS: MOSTLY FUNCTIONAL ({failed} issues)")
print(f"🔧 The system is mostly working but needs attention on failed tests")
else:
print(f"\n❌ OVERALL STATUS: SYSTEM ISSUES DETECTED")
print(f"🚨 Multiple test failures indicate systemic problems")
print(f"\n💡 Recommendations:")
if failed > 0:
print(f" • Investigate failed tests above")
print(f" • Check blockchain node logs for errors")
print(f" • Verify network connectivity and service status")
else:
print(f" • System is healthy - continue monitoring")
print(f" • Consider running load/stress tests next")
return {
"total_tests": total,
"passed": passed,
"failed": failed,
"skipped": skipped,
"success_rate": success_rate,
"total_duration": total_duration,
"timestamp": datetime.now().isoformat(),
"results": self.test_results
}
def main():
"""Main test execution"""
try:
tester = AITCBE2ETest()
summary = tester.run_all_tests()
# Exit with appropriate code
if summary["failed"] == 0:
sys.exit(0) # Success
elif summary["passed"] >= summary["total_tests"] * 0.5:
sys.exit(1) # Partial success
else:
sys.exit(2) # Failure
except KeyboardInterrupt:
print("\n🛑 Test interrupted by user")
sys.exit(130)
except Exception as e:
print(f"\n💥 Test suite crashed: {str(e)}")
sys.exit(1)
if __name__ == "__main__":
main()