From fd6ade31dc21421bdaa1739b889bc1d3b0861816 Mon Sep 17 00:00:00 2001 From: aitbc Date: Mon, 25 May 2026 10:08:41 +0200 Subject: [PATCH] fix: remove backup files and fix zero-address defaults - Removed router_old.py (2473 lines - backup reference file) - Removed cli/aitbc_cli.legacy.py (3256 lines - legacy CLI) - Fixed zero-address defaults in dispute_resolution.py: - Dispute.winner: changed from "0x0000000000000000000000000000000000000000" to None - Evidence.verified_by: changed from "0x0000000000000000000000000000000000000000" to None These changes address remaining cleanup items from the gap analysis. --- .../contracts/dispute_resolution.py | 4 +- .../src/aitbc_chain/rpc/router_old.py | 2473 ------------- cli/aitbc_cli.legacy.py | 3256 ----------------- 3 files changed, 2 insertions(+), 5731 deletions(-) delete mode 100644 apps/blockchain-node/src/aitbc_chain/rpc/router_old.py delete mode 100755 cli/aitbc_cli.legacy.py diff --git a/apps/blockchain-node/src/aitbc_chain/contracts/dispute_resolution.py b/apps/blockchain-node/src/aitbc_chain/contracts/dispute_resolution.py index 0b99d8c5..e987ce06 100644 --- a/apps/blockchain-node/src/aitbc_chain/contracts/dispute_resolution.py +++ b/apps/blockchain-node/src/aitbc_chain/contracts/dispute_resolution.py @@ -46,7 +46,7 @@ class Dispute: evidence_deadline: int arbitration_deadline: int resolution_amount: int = 0 - winner: str = "0x0000000000000000000000000000000000000000" + winner: str | None = None resolution_reason: str = "" arbitrator_count: int = 0 is_escalated: bool = False @@ -64,7 +64,7 @@ class Evidence: submission_time: int is_valid: bool = False verification_score: int = 0 - verified_by: str = "0x0000000000000000000000000000000000000000" + verified_by: str | None = None @dataclass diff --git a/apps/blockchain-node/src/aitbc_chain/rpc/router_old.py b/apps/blockchain-node/src/aitbc_chain/rpc/router_old.py deleted file mode 100644 index 6bc08b3d..00000000 --- a/apps/blockchain-node/src/aitbc_chain/rpc/router_old.py +++ /dev/null @@ -1,2473 +0,0 @@ -from __future__ import annotations - -import asyncio -import time -from typing import Any, Dict, Optional, List -from datetime import datetime, timezone, timedelta - -from fastapi import APIRouter, Depends, HTTPException, status, Request -from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials -from pydantic import BaseModel, Field, model_validator -from sqlmodel import select, delete - -from ..database import session_scope, get_engine -from ..gossip import gossip_broker -from ..mempool import get_mempool -from ..metrics import metrics_registry -from ..models import Account, Block, Receipt, Transaction -from ..logger import get_logger -from ..sync import ChainSync -from .auth import get_authenticated_address -from .utils import ( - set_poa_proposer, - get_poa_proposer, - get_chain_id, - validate_chain_id, - get_supported_chains, - get_chain_db, - normalize_transaction_data, -) - -from aitbc.rate_limiting import rate_limit - -# Import domain modules -from .blocks import ( - get_genesis_allocations, - get_head, - get_block, - get_blocks_range, - import_block, -) -from .transactions import ( - submit_transaction, - get_mempool, - submit_marketplace_transaction, - query_transactions, - TransactionRequest, -) -from .accounts import ( - get_account, - get_account_alias, - get_account_details, - create_account, - faucet_request, - get_balance_breakdown, - reconcile_balance, -) -from .disputes import ( - file_dispute, - submit_evidence, - verify_evidence, - submit_arbitration_vote, - authorize_arbitrator, - get_active_disputes, - get_authorized_arbitrators, - get_arbitrator_disputes, - get_user_disputes, - get_dispute, - get_dispute_evidence, - get_arbitration_votes, -) -from .contracts import ( - deploy_messaging_contract, - list_contracts, - deploy_contract, - call_contract, - verify_contract, - get_messaging_contract_state, - get_forum_topics, - create_forum_topic, - get_topic_messages, - post_message, - vote_message, - search_messages, - get_agent_reputation, - moderate_message, -) -from .sync import ( - export_chain, - import_chain, - force_sync, -) -from .gossip import ( - get_logs, - GetLogsRequest, - GetLogsResponse, -) -from .islands import ( - join_island, - leave_island, - list_islands, - get_island, - request_bridge, - JoinIslandRequest, - JoinIslandResponse, - LeaveIslandRequest, - LeaveIslandResponse, - BridgeRequestRequest, - BridgeRequestResponse, -) -from .bridge import ( - bridge_lock, - bridge_confirm, - get_bridge_transfer, - list_pending_transfers, -) -from .staking import ( - stake_tokens, - unstake_tokens, - get_staking_info, -) - -_logger = get_logger(__name__) - -# Security scheme for authentication -security = HTTPBearer(auto_error=False) - -router = APIRouter() - -# Global rate limiter for importBlock -_last_import_time = 0 -_import_lock = asyncio.Lock() - - - - - -@router.get("/genesis_allocations", summary="Get genesis allocations from blockchain") -@rate_limit(rate=200, per=60) -async def get_genesis_allocations( - request: Request, chain_id: str = None -) -> Dict[str, Any]: - """Get genesis allocations from genesis block metadata for RPC bootstrap""" - from .blocks import get_genesis_allocations as _get_genesis_allocations - return await _get_genesis_allocations(request, chain_id) - - -@router.get("/head", summary="Get current chain head") -@rate_limit(rate=200, per=60) -async def get_head( - request: Request, chain_id: str = None -) -> Dict[str, Any]: - """Get current chain head""" - from .blocks import get_head as _get_head - return await _get_head(request, chain_id) - - -@router.get("/blocks/{height}", summary="Get block by height") -@rate_limit(rate=200, per=60) -async def get_block( - request: Request, height: int, chain_id: str = None -) -> Dict[str, Any]: - """Get block by height""" - from .blocks import get_block as _get_block - return await _get_block(request, height, chain_id) - - -@router.post("/transaction", summary="Submit transaction") -@rate_limit(rate=50, per=60) -async def submit_transaction( - request: Request, tx_data: TransactionRequest -) -> Dict[str, Any]: - """Submit a new transaction to the mempool""" - from ..mempool import get_mempool - - try: - mempool = get_mempool() - chain_id = get_chain_id(None) - - # Convert TransactionRequest to dict for normalization - # Model validator already normalized payload, so use 'to' directly from payload - tx_data_dict = { - "from": tx_data.sender, - "to": tx_data.payload.get("to"), # Model validator sets this from recipient/to - "amount": tx_data.payload.get("amount", tx_data.payload.get("value", 0)), - "fee": tx_data.fee, - "nonce": tx_data.nonce, - "payload": tx_data.payload, - "type": tx_data.type, - "signature": tx_data.sig - } - - tx_data_dict = normalize_transaction_data(tx_data_dict, chain_id) - _validate_transaction_admission(tx_data_dict, mempool) - - tx_hash = mempool.add(tx_data_dict, chain_id=chain_id) - - 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") -@rate_limit(rate=200, per=60) -async def get_mempool( - request: Request, 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(f"Failed to get mempool", extra={"error": str(e)}) - raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to get mempool: {str(e)}") - - -@router.get("/account/{address}", summary="Get account information") -@rate_limit(rate=200, per=60) -async def get_account( - request: Request, address: str, chain_id: str = None -) -> Dict[str, Any]: - """Get account information""" - chain_id = get_chain_id(chain_id) - - with session_scope() as session: - account = session.exec(select(Account).where(Account.address == address).where(Account.chain_id == chain_id)).first() - if not account: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Account not found") - - return { - "address": account.address, - "balance": account.balance, - "nonce": account.nonce, - "chain_id": account.chain_id - } - - -@router.get("/accounts/{address}", summary="Get account information (alias)") -@rate_limit(rate=200, per=60) -async def get_account_alias( - request: Request, address: str, chain_id: str = None -) -> Dict[str, Any]: - """Get account information (alias endpoint)""" - return await get_account(address, chain_id) - - -@router.post("/transactions/marketplace", summary="Submit marketplace transaction") -@rate_limit(rate=50, per=60) -async def submit_marketplace_transaction( - request: Request, tx_data: Dict[str, Any] -) -> Dict[str, Any]: - """Submit a marketplace purchase transaction to the blockchain""" - from ..config import settings as cfg - chain_id = get_chain_id(tx_data.get("chain_id")) - - metrics_registry.increment("rpc_marketplace_transaction_total") - start = time.perf_counter() - - try: - with session_scope() as session: - # Validate sender account - sender_addr = tx_data.get("from") - sender_account = session.get(Account, (chain_id, sender_addr)) - if not sender_account: - raise ValueError(f"Sender account not found: {sender_addr}") - - # Validate balance - amount = tx_data.get("value", 0) - fee = tx_data.get("fee", 0) - total_cost = amount + fee - - if sender_account.balance < total_cost: - raise ValueError(f"Insufficient balance: {sender_account.balance} < {total_cost}") - - # Validate nonce - tx_nonce = tx_data.get("nonce", 0) - if tx_nonce != sender_account.nonce: - raise ValueError(f"Invalid nonce: expected {sender_account.nonce}, got {tx_nonce}") - - # Get or create recipient account - recipient_addr = tx_data.get("to") - recipient_account = session.get(Account, (chain_id, recipient_addr)) - if not recipient_account: - recipient_account = Account( - chain_id=chain_id, - address=recipient_addr, - balance=0, - nonce=0 - ) - session.add(recipient_account) - - # Create transaction record - tx_hash = compute_tx_hash(tx_data) - transaction = Transaction( - chain_id=chain_id, - tx_hash=tx_hash, - sender=sender_addr, - recipient=recipient_addr, - payload=tx_data.get("payload", {}), - created_at=datetime.now(timezone.utc), - nonce=tx_nonce, - value=amount, - fee=fee, - status="pending", - timestamp=datetime.now(timezone.utc).isoformat() - ) - session.add(transaction) - - # Update account balances (pending state) - sender_account.balance -= total_cost - sender_account.nonce += 1 - recipient_account.balance += amount - - metrics_registry.increment("rpc_marketplace_transaction_success") - duration = time.perf_counter() - start - metrics_registry.observe("rpc_marketplace_transaction_duration_seconds", duration) - - _logger.info(f"Marketplace transaction submitted: {tx_hash[:16]}... from {sender_addr[:16]}... to {recipient_addr[:16]}... amount={amount}") - - return { - "success": True, - "tx_hash": tx_hash, - "status": "pending", - "chain_id": chain_id, - "amount": amount, - "fee": fee, - "from": sender_addr, - "to": recipient_addr - } - - except ValueError as e: - metrics_registry.increment("rpc_marketplace_transaction_validation_errors_total") - _logger.error(f"Marketplace transaction validation failed: {str(e)}") - raise HTTPException(status_code=400, detail=str(e)) - except Exception as e: - metrics_registry.increment("rpc_marketplace_transaction_errors_total") - _logger.error(f"Failed to submit marketplace transaction", extra={"error": str(e)}) - raise HTTPException(status_code=500, detail=f"Failed to submit marketplace transaction: {str(e)}") - - -@router.get("/transactions", summary="Query transactions") -@rate_limit(rate=200, per=60) -async def query_transactions( - request: Request, - transaction_type: Optional[str] = None, - island_id: Optional[str] = None, - pair: Optional[str] = None, - status: Optional[str] = None, - order_id: Optional[str] = None, - limit: Optional[int] = 100, - chain_id: str = None -) -> List[Dict[str, Any]]: - """Query transactions with optional filters""" - chain_id = get_chain_id(chain_id) - - with session_scope() as session: - query = select(Transaction).where(Transaction.chain_id == chain_id) - - # Apply filters based on payload fields - transactions = session.exec(query).all() - - results = [] - for tx in transactions: - # Filter by transaction type in payload - if transaction_type and tx.payload.get('type') != transaction_type: - continue - - # Filter by island_id in payload - if island_id and tx.payload.get('island_id') != island_id: - continue - - # Filter by pair in payload - if pair and tx.payload.get('pair') != pair: - continue - - # Filter by status in payload - if status and tx.payload.get('status') != status: - continue - - # Filter by order_id in payload - if order_id and tx.payload.get('order_id') != order_id and tx.payload.get('offer_id') != order_id and tx.payload.get('bid_id') != order_id: - continue - - results.append({ - "transaction_id": tx.id, - "tx_hash": tx.tx_hash, - "sender": tx.sender, - "recipient": tx.recipient, - "payload": tx.payload, - "status": tx.status, - "created_at": tx.created_at.isoformat(), - "timestamp": tx.timestamp, - "nonce": tx.nonce, - "value": tx.value, - "fee": tx.fee - }) - - # Apply limit - if limit: - results = results[:limit] - - return results - - -@router.get("/blocks-range", summary="Get blocks in height range") -@rate_limit(rate=200, per=60) -async def get_blocks_range( - request: Request, start: int = 0, end: int = 10, include_tx: bool = True, chain_id: str = None -) -> Dict[str, Any]: - """Get blocks in a height range - - Args: - start: Starting block height (inclusive) - end: Ending block height (inclusive) - include_tx: Whether to include transaction data (default: True) - """ - with session_scope() as session: - from ..models import Transaction - chain_id = get_chain_id(chain_id) - - blocks = session.exec( - select(Block).where( - Block.chain_id == chain_id, - Block.height >= start, - Block.height <= end, - ).order_by(Block.height.asc()) - ).all() - - result_blocks = [] - for b in blocks: - block_data = { - "height": b.height, - "hash": b.hash, - "parent_hash": b.parent_hash, - "proposer": b.proposer, - "timestamp": b.timestamp.isoformat(), - "tx_count": b.tx_count, - "state_root": b.state_root, - } - - if include_tx: - # Fetch transactions for this block - txs = session.exec( - select(Transaction) - .where(Transaction.chain_id == chain_id) - .where(Transaction.block_height == b.height) - ).all() - block_data["transactions"] = [tx.model_dump() for tx in txs] - - result_blocks.append(block_data) - - return { - "success": True, - "blocks": result_blocks, - "count": len(blocks), - } - -@router.post("/contracts/deploy/messaging", summary="Deploy messaging contract") -@rate_limit(rate=50, per=60) -async def deploy_messaging_contract( - request: Request, deploy_data: dict -) -> Dict[str, Any]: - """Deploy the agent messaging contract to the blockchain""" - contract_address = "0xagent_messaging_001" - return {"success": True, "contract_address": contract_address, "status": "deployed"} - -@router.get("/contracts", summary="List deployed contracts") -@rate_limit(rate=200, per=60) -async def list_contracts( - request: Request -) -> Dict[str, Any]: - """List all deployed contracts""" - return contract_service.list_contracts() - -@router.post("/contracts/deploy", summary="Deploy a smart contract") -@rate_limit(rate=50, per=60) -async def deploy_contract( - request: Request, deploy_data: dict -) -> Dict[str, Any]: - """Deploy a new smart contract to the blockchain""" - contract_name = deploy_data.get("name") - contract_type = deploy_data.get("type", "zk-verifier") - - if not contract_name: - return {"success": False, "error": "Contract name is required"} - - # Generate a mock contract address for now - contract_address = f"0x{contract_name.lower()}_{int(time.time())}" - - return { - "success": True, - "contract_address": contract_address, - "name": contract_name, - "type": contract_type, - "status": "deployed", - "deployed_at": datetime.now(UTC).isoformat() - } - -@router.post("/contracts/call", summary="Call a contract method") -@rate_limit(rate=50, per=60) -async def call_contract( - request: Request, call_data: dict -) -> Dict[str, Any]: - """Call a method on a deployed contract""" - contract_address = call_data.get("address") - method = call_data.get("method") - params = call_data.get("params") - - if not contract_address: - return {"success": False, "error": "Contract address is required"} - if not method: - return {"success": False, "error": "Method name is required"} - - # Mock call result for now - return { - "success": True, - "result": f"Called {method} on {contract_address}", - "address": contract_address, - "method": method - } - -@router.post("/contracts/verify", summary="Verify a ZK proof") -@rate_limit(rate=50, per=60) -async def verify_contract( - request: Request, verify_data: dict -) -> Dict[str, Any]: - """Verify a ZK proof against a contract""" - contract_address = verify_data.get("address") - proof = verify_data.get("proof") - - if not contract_address: - return {"success": False, "error": "Contract address is required"} - - # Mock verification result for now - return { - "success": True, - "result": { - "valid": True, - "receipt_hash": "0xmock_receipt_hash", - "address": contract_address - } - } - -@router.get("/contracts/messaging/state", summary="Get messaging contract state") -@rate_limit(rate=200, per=60) -async def get_messaging_contract_state( - request: Request -) -> Dict[str, Any]: - """Get the current state of the messaging contract""" - state = { - "total_topics": len(messaging_contract.topics), - "total_messages": len(messaging_contract.messages), - "total_agents": len(messaging_contract.agent_reputations) - } - return {"success": True, "contract_state": state} - -@router.get("/messaging/topics", summary="Get forum topics") -@rate_limit(rate=200, per=60) -async def get_forum_topics( - request: Request, limit: int = 50, offset: int = 0, sort_by: str = "last_activity" -) -> Dict[str, Any]: - """Get list of forum topics""" - return messaging_contract.get_topics(limit, offset, sort_by) - -@router.post("/messaging/topics/create", summary="Create forum topic") -@rate_limit(rate=50, per=60) -async def create_forum_topic( - request: Request, topic_data: dict -) -> Dict[str, Any]: - """Create a new forum topic""" - return messaging_contract.create_topic( - topic_data.get("agent_id"), - topic_data.get("agent_address"), - topic_data.get("title"), - topic_data.get("description"), - topic_data.get("tags", []) - ) - -@router.get("/messaging/topics/{topic_id}/messages", summary="Get topic messages") -@rate_limit(rate=200, per=60) -async def get_topic_messages( - request: Request, topic_id: str, limit: int = 50, offset: int = 0, sort_by: str = "timestamp" -) -> Dict[str, Any]: - """Get messages from a forum topic""" - return messaging_contract.get_messages(topic_id, limit, offset, sort_by) - -@router.post("/messaging/messages/post", summary="Post message") -@rate_limit(rate=50, per=60) -async def post_message( - request: Request, message_data: dict -) -> Dict[str, Any]: - """Post a message to a forum topic""" - return messaging_contract.post_message( - message_data.get("agent_id"), - message_data.get("agent_address"), - message_data.get("topic_id"), - message_data.get("content"), - message_data.get("message_type", "post"), - message_data.get("parent_message_id") - ) - -@router.post("/messaging/messages/{message_id}/vote", summary="Vote on message") -@rate_limit(rate=50, per=60) -async def vote_message( - request: Request, message_id: str, vote_data: dict -) -> Dict[str, Any]: - """Vote on a message (upvote/downvote)""" - return messaging_contract.vote_message( - vote_data.get("agent_id"), - vote_data.get("agent_address"), - message_id, - vote_data.get("vote_type") - ) - -@router.get("/messaging/messages/search", summary="Search messages") -@rate_limit(rate=200, per=60) -async def search_messages( - request: Request, query: str, limit: int = 50 -) -> Dict[str, Any]: - """Search messages by content""" - return messaging_contract.search_messages(query, limit) - -@router.get("/messaging/agents/{agent_id}/reputation", summary="Get agent reputation") -@rate_limit(rate=200, per=60) -async def get_agent_reputation( - request: Request, agent_id: str -) -> Dict[str, Any]: - """Get agent reputation information""" - return messaging_contract.get_agent_reputation(agent_id) - -@router.post("/messaging/messages/{message_id}/moderate", summary="Moderate message") -@rate_limit(rate=50, per=60) -async def moderate_message( - request: Request, message_id: str, moderation_data: dict -) -> Dict[str, Any]: - """Moderate a message (moderator only)""" - return messaging_contract.moderate_message( - moderation_data.get("moderator_agent_id"), - moderation_data.get("moderator_address"), - message_id, - moderation_data.get("action"), - moderation_data.get("reason", "") - ) - -@router.post("/importBlock", summary="Import a block") -@rate_limit(rate=50, per=60) -async def import_block( - request: Request, block_data: dict -) -> Dict[str, Any]: - """Import a block into the blockchain""" - global _last_import_time - - async with _import_lock: - try: - # Rate limiting: max 1 import per second - current_time = time.time() - time_since_last = current_time - _last_import_time - if time_since_last < 1.0: - await asyncio.sleep(1.0 - time_since_last) - - _last_import_time = time.time() - - chain_id = block_data.get("chain_id") or block_data.get("chainId") or get_chain_id(None) - block_hash = block_data["hash"] - - # Validate block hash format: must be 0x followed by exactly 64 hex characters - if not isinstance(block_hash, str) or not re.fullmatch(r"0x[0-9a-fA-F]{64}", block_hash): - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid block hash format") - - try: - block_height = int(block_data["height"]) - except (KeyError, TypeError, ValueError) as exc: - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid block height") from exc - - timestamp = block_data.get("timestamp") - if isinstance(timestamp, str): - try: - timestamp = datetime.fromisoformat(timestamp.replace('Z', '+00:00')) - except ValueError: - timestamp = datetime.now(timezone.utc) - elif timestamp is None: - timestamp = datetime.now(timezone.utc) - - with session_scope(chain_id) as session: - existing_height_block = session.exec( - select(Block) - .where(Block.chain_id == chain_id) - .where(Block.height == block_height) - ).first() - if existing_height_block is not None: - if existing_height_block.hash == block_hash: - return { - "success": True, - "block_height": existing_height_block.height, - "block_hash": existing_height_block.hash, - "chain_id": chain_id - } - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, - detail=f"Block height {block_height} already exists with different hash", - ) - - # Validate parent block exists (skip for genesis block height 1) - parent_hash = block_data["parent_hash"] - if block_height > 1: - parent_block = session.exec( - select(Block).where(Block.hash == parent_hash) - ).first() - if parent_block is None: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Parent block not found", - ) - - # Check for hash conflicts across chains - existing_block = session.execute( - select(Block).where(Block.hash == block_hash) - ).first() - - if existing_block: - # Delete existing block with conflicting hash - _logger.warning(f"Deleting existing block with conflicting hash {block_hash} from chain {existing_block[0].chain_id}") - session.execute(delete(Block).where(Block.hash == block_hash)) - session.commit() - - # Create block - block = Block( - chain_id=chain_id, - height=block_height, - hash=block_hash, - parent_hash=block_data["parent_hash"], - proposer=block_data["proposer"], - timestamp=timestamp, - state_root=block_data.get("state_root"), - tx_count=block_data.get("tx_count", 0) - ) - session.add(block) - session.commit() - - return { - "success": True, - "block_height": block.height, - "block_hash": block.hash, - "chain_id": chain_id - } - except HTTPException: - raise - except Exception as e: - _logger.error(f"Error importing block: {e}") - raise HTTPException(status_code=500, detail=f"Failed to import block: {str(e)}") - -def _serialize_optional_timestamp(value: Any) -> Optional[str]: - if value is None: - return None - if isinstance(value, str): - return value - if hasattr(value, "isoformat"): - return value.isoformat() - return str(value) - -def _parse_datetime_value(value: Any, field_name: str) -> Optional[datetime]: - if value in (None, ""): - return None - if isinstance(value, datetime): - return value - if isinstance(value, str): - try: - return datetime.fromisoformat(value.replace("Z", "+00:00")) - except ValueError as exc: - raise HTTPException(status_code=400, detail=f"Invalid {field_name}: {value}") from exc - raise HTTPException(status_code=400, detail=f"Invalid {field_name} type: {type(value).__name__}") - -def _select_export_blocks(session, chain_id: str) -> List[Block]: - blocks_result = session.execute( - select(Block) - .where(Block.chain_id == chain_id) - .order_by(Block.height.asc(), Block.id.desc()) - ) - blocks: List[Block] = [] - seen_heights = set() - duplicate_count = 0 - for block in blocks_result.scalars().all(): - if block.height in seen_heights: - duplicate_count += 1 - continue - seen_heights.add(block.height) - blocks.append(block) - if duplicate_count: - _logger.warning(f"Filtered {duplicate_count} duplicate exported blocks for chain {chain_id}") - return blocks - -def _dedupe_import_blocks(blocks: List[Dict[str, Any]], chain_id: str) -> List[Dict[str, Any]]: - latest_by_height: Dict[int, Dict[str, Any]] = {} - duplicate_count = 0 - for block_data in blocks: - if "height" not in block_data: - raise HTTPException(status_code=400, detail="Block height is required") - try: - height = int(block_data["height"]) - except (TypeError, ValueError) as exc: - raise HTTPException(status_code=400, detail=f"Invalid block height: {block_data.get('height')}") from exc - block_chain_id = block_data.get("chain_id") - if block_chain_id and block_chain_id != chain_id: - raise HTTPException( - status_code=400, - detail=f"Mismatched block chain_id '{block_chain_id}' for import chain '{chain_id}'", - ) - normalized_block = dict(block_data) - normalized_block["height"] = height - normalized_block["chain_id"] = chain_id - if height in latest_by_height: - duplicate_count += 1 - latest_by_height[height] = normalized_block - if duplicate_count: - _logger.warning(f"Filtered {duplicate_count} duplicate imported blocks for chain {chain_id}") - return [latest_by_height[height] for height in sorted(latest_by_height)] - -@router.get("/export-chain", summary="Export full chain state") -@rate_limit(rate=200, per=60) -async def export_chain( - request: Request, chain_id: str = None -) -> Dict[str, Any]: - """Export full chain state as JSON for manual synchronization""" - chain_id = get_chain_id(chain_id) - try: - # Use session_scope for database operations - with session_scope() as session: - blocks = _select_export_blocks(session, chain_id) - - accounts_result = session.execute( - select(Account) - .where(Account.chain_id == chain_id) - .order_by(Account.address) - ) - accounts = list(accounts_result.scalars().all()) - - txs_result = session.execute( - select(Transaction) - .where(Transaction.chain_id == chain_id) - .order_by(Transaction.block_height, Transaction.id) - ) - transactions = list(txs_result.scalars().all()) - - # Build export data - export_data = { - "chain_id": chain_id, - "export_timestamp": datetime.now().isoformat(), - "block_count": len(blocks), - "account_count": len(accounts), - "transaction_count": len(transactions), - "blocks": [ - { - "chain_id": b.chain_id, - "height": b.height, - "hash": b.hash, - "parent_hash": b.parent_hash, - "proposer": b.proposer, - "timestamp": b.timestamp.isoformat() if b.timestamp else None, - "state_root": b.state_root, - "tx_count": b.tx_count, - "block_metadata": b.block_metadata, - } - for b in blocks - ], - "accounts": [ - { - "chain_id": a.chain_id, - "address": a.address, - "balance": a.balance, - "nonce": a.nonce - } - for a in accounts - ], - "transactions": [ - { - "id": t.id, - "chain_id": t.chain_id, - "tx_hash": t.tx_hash, - "block_height": t.block_height, - "sender": t.sender, - "recipient": t.recipient, - "payload": t.payload, - "value": t.value, - "fee": t.fee, - "nonce": t.nonce, - "timestamp": _serialize_optional_timestamp(t.timestamp), - "status": t.status, - "created_at": t.created_at.isoformat() if t.created_at else None, - "tx_metadata": t.tx_metadata, - } - for t in transactions - ] - } - - return { - "success": True, - "export_data": export_data, - "export_size_bytes": len(json.dumps(export_data)) - } - except HTTPException: - raise - except Exception as e: - _logger.error(f"Error exporting chain: {e}") - raise HTTPException(status_code=500, detail=f"Failed to export chain: {str(e)}") - -@router.post("/import-chain", summary="Import chain state") -@rate_limit(rate=50, per=60) -async def import_chain( - request: Request, import_data: dict -) -> Dict[str, Any]: - """Import chain state from JSON for manual synchronization""" - async with _import_lock: - try: - chain_id = import_data.get("chain_id") - blocks = import_data.get("blocks", []) - accounts = import_data.get("accounts", []) - transactions = import_data.get("transactions", []) - - if not chain_id and blocks: - chain_id = blocks[0].get("chain_id") - chain_id = get_chain_id(chain_id) - - unique_blocks = _dedupe_import_blocks(blocks, chain_id) - - with session_scope() as session: - if not unique_blocks: - raise HTTPException(status_code=400, detail="No blocks to import") - - existing_blocks = session.execute( - select(Block) - .where(Block.chain_id == chain_id) - .order_by(Block.height) - ) - existing_count = len(list(existing_blocks.scalars().all())) - - if existing_count > 0: - _logger.info(f"Backing up existing chain with {existing_count} blocks") - - _logger.info(f"Clearing existing transactions for chain {chain_id}") - session.execute(delete(Transaction).where(Transaction.chain_id == chain_id)) - if accounts: - _logger.info(f"Clearing existing accounts for chain {chain_id}") - session.execute(delete(Account).where(Account.chain_id == chain_id)) - _logger.info(f"Clearing existing blocks for chain {chain_id}") - session.execute(delete(Block).where(Block.chain_id == chain_id)) - - import_hashes = {block_data["hash"] for block_data in unique_blocks} - if import_hashes: - hash_conflict_result = session.execute( - select(Block.hash, Block.chain_id) - .where(Block.hash.in_(import_hashes)) - ) - hash_conflicts = hash_conflict_result.all() - if hash_conflicts: - conflict_chains = {chain_id for _, chain_id in hash_conflicts} - _logger.warning(f"Clearing {len(hash_conflicts)} blocks with conflicting hashes across chains: {conflict_chains}") - session.execute(delete(Block).where(Block.hash.in_(import_hashes))) - - session.commit() - session.expire_all() - - _logger.info(f"Importing {len(unique_blocks)} unique blocks (filtered from {len(blocks)} total)") - - for block_data in unique_blocks: - block_timestamp = _parse_datetime_value(block_data.get("timestamp"), "block timestamp") or datetime.now(timezone.utc) - block = Block( - chain_id=chain_id, - height=block_data["height"], - hash=block_data["hash"], - parent_hash=block_data["parent_hash"], - proposer=block_data["proposer"], - timestamp=block_timestamp, - state_root=block_data.get("state_root"), - tx_count=block_data.get("tx_count", 0), - block_metadata=block_data.get("block_metadata"), - ) - session.add(block) - - for account_data in accounts: - account_chain_id = account_data.get("chain_id", chain_id) - if account_chain_id != chain_id: - raise HTTPException( - status_code=400, - detail=f"Mismatched account chain_id '{account_chain_id}' for import chain '{chain_id}'", - ) - account = Account( - chain_id=account_chain_id, - address=account_data["address"], - balance=account_data["balance"], - nonce=account_data["nonce"], - ) - session.add(account) - - for tx_data in transactions: - tx_chain_id = tx_data.get("chain_id", chain_id) - if tx_chain_id != chain_id: - raise HTTPException( - status_code=400, - detail=f"Mismatched transaction chain_id '{tx_chain_id}' for import chain '{chain_id}'", - ) - tx = Transaction( - id=tx_data.get("id"), - chain_id=tx_chain_id, - tx_hash=str(tx_data.get("tx_hash") or tx_data.get("id") or ""), - block_height=tx_data.get("block_height"), - sender=tx_data["sender"], - recipient=tx_data["recipient"], - payload=tx_data.get("payload", {}), - value=tx_data.get("value", 0), - fee=tx_data.get("fee", 0), - nonce=tx_data.get("nonce", 0), - timestamp=_serialize_optional_timestamp(tx_data.get("timestamp")), - status=tx_data.get("status", "pending"), - tx_metadata=tx_data.get("tx_metadata"), - ) - created_at = _parse_datetime_value(tx_data.get("created_at"), "transaction created_at") - if created_at is not None: - tx.created_at = created_at - session.add(tx) - - session.commit() - - return { - "success": True, - "imported_blocks": len(unique_blocks), - "imported_accounts": len(accounts), - "imported_transactions": len(transactions), - "chain_id": chain_id, - "message": f"Successfully imported {len(unique_blocks)} blocks", - } - - except HTTPException: - raise - except Exception as e: - _logger.error(f"Error importing chain: {e}") - raise HTTPException(status_code=500, detail=f"Failed to import chain: {str(e)}") - -@router.post("/force-sync", summary="Force reorg to specified peer") -@rate_limit(rate=50, per=60) -async def force_sync( - request: Request, peer_data: dict -) -> Dict[str, Any]: - """Force blockchain reorganization to sync with specified peer""" - try: - peer_url = peer_data.get("peer_url") - target_height = peer_data.get("target_height") - - if not peer_url: - raise HTTPException(status_code=400, detail="peer_url is required") - - # Validate peer_url to prevent SSRF - import re - from urllib.parse import urlparse - - parsed = urlparse(peer_url) - if not parsed.scheme or parsed.scheme not in ['http', 'https']: - raise HTTPException(status_code=400, detail="Invalid URL scheme") - - # Block private/internal IPs - hostname = parsed.hostname - if hostname: - # Block localhost and private IP ranges - if hostname in ['localhost', '127.0.0.1', '::1'] or hostname.startswith('192.168.') or hostname.startswith('10.') or hostname.startswith('172.16.'): - raise HTTPException(status_code=400, detail="Invalid peer URL") - - import requests - - response = requests.get(f"{peer_url}/rpc/export-chain", timeout=30) - - if response.status_code != 200: - raise HTTPException(status_code=400, detail=f"Failed to fetch peer chain: {response.status_code}") - - peer_chain_data = response.json() - peer_blocks = peer_chain_data["export_data"]["blocks"] - - if target_height and len(peer_blocks) < target_height: - raise HTTPException(status_code=400, detail=f"Peer only has {len(peer_blocks)} blocks, cannot sync to height {target_height}") - - import_result = await import_chain(peer_chain_data["export_data"]) - - return { - "success": True, - "synced_from": peer_url, - "synced_blocks": import_result["imported_blocks"], - "target_height": target_height or import_result["imported_blocks"], - "message": f"Successfully synced with peer {peer_url}" - } - - except HTTPException: - raise - except Exception as e: - _logger.error(f"Error forcing sync: {e}") - raise HTTPException(status_code=500, detail=f"Failed to force sync: {str(e)}") - - -class GetLogsRequest(BaseModel): - """Request model for eth_getLogs RPC endpoint.""" - address: Optional[str] = Field(None, description="Contract address to filter logs") - from_block: Optional[int] = Field(None, description="Starting block height") - to_block: Optional[int] = Field(None, description="Ending block height") - topics: Optional[List[str]] = Field(None, description="Event topics to filter") - - -class LogEntry(BaseModel): - """Single log entry from smart contract event.""" - address: str - topics: List[str] - data: str - block_number: int - transaction_hash: str - log_index: int - - -class GetLogsResponse(BaseModel): - """Response model for eth_getLogs RPC endpoint.""" - logs: List[LogEntry] - count: int - - -@router.post("/eth_getLogs", summary="Query smart contract event logs") -@rate_limit(rate=200, per=60) -async def get_logs( - request: Request, - logs_request: GetLogsRequest, - chain_id: Optional[str] = None -) -> GetLogsResponse: - """ - Query smart contract event logs using eth_getLogs-compatible endpoint. - Filters Receipt model for logs matching contract address and event topics. - """ - chain_id = get_chain_id(chain_id) - - with session_scope() as session: - # Build query for receipts - query = select(Receipt).where(Receipt.chain_id == chain_id) - - # Filter by block range - if request.from_block is not None: - query = query.where(Receipt.block_height >= request.from_block) - if request.to_block is not None: - query = query.where(Receipt.block_height <= request.to_block) - - # Execute query - receipts = session.execute(query).scalars().all() - - logs = [] - for receipt in receipts: - # Extract event logs from receipt payload - payload = receipt.payload or {} - events = payload.get("events", []) - - for event in events: - # Filter by contract address if specified - if request.address and event.get("address") != request.address: - continue - - # Filter by topics if specified - if request.topics: - event_topics = event.get("topics", []) - if not any(topic in event_topics for topic in request.topics): - continue - - # Create log entry - log_entry = LogEntry( - address=event.get("address", ""), - topics=event.get("topics", []), - data=str(event.get("data", "")), - block_number=receipt.block_height or 0, - transaction_hash=receipt.receipt_id, - log_index=event.get("logIndex", 0) - ) - logs.append(log_entry) - - return GetLogsResponse(logs=logs, count=len(logs)) - - -# Island Management Endpoints for Edge API -class JoinIslandRequest(BaseModel): - """Request model for joining an island""" - island_id: str - island_name: str - chain_id: str - role: str = "compute-provider" - is_hub: bool = False - - -class JoinIslandResponse(BaseModel): - """Response model for joining an island""" - success: bool - island_id: str - status: str - message: str - - -class LeaveIslandRequest(BaseModel): - """Request model for leaving an island""" - island_id: str - - -class LeaveIslandResponse(BaseModel): - """Response model for leaving an island""" - success: bool - island_id: str - status: str - message: str - - -class BridgeRequestRequest(BaseModel): - """Request model for requesting a bridge""" - target_island_id: str - - -class BridgeRequestResponse(BaseModel): - """Response model for bridge request""" - success: bool - request_id: str - target_island_id: str - status: str - message: str - - -# Dispute Resolution Endpoints -class FileDisputeRequest(BaseModel): - """Request model for filing a dispute""" - agreement_id: int = Field(description="ID of the agreement being disputed") - respondent: str = Field(description="Address of the respondent") - dispute_type: str = Field(description="Type of dispute (Performance, Payment, ServiceQuality, Availability, Other)") - reason: str = Field(description="Reason for the dispute") - evidence_hash: str = Field(description="Hash of initial evidence") - - -class FileDisputeResponse(BaseModel): - """Response model for filing a dispute""" - success: bool - dispute_id: int - status: str - message: str - - -class SubmitEvidenceRequest(BaseModel): - """Request model for submitting evidence""" - dispute_id: int = Field(description="ID of the dispute") - evidence_type: str = Field(description="Type of evidence") - evidence_data: str = Field(description="Evidence data (IPFS hash, URL, etc.)") - - -class SubmitEvidenceResponse(BaseModel): - """Response model for submitting evidence""" - success: bool - evidence_id: int - status: str - message: str - - -class VerifyEvidenceRequest(BaseModel): - """Request model for verifying evidence""" - dispute_id: int = Field(description="ID of the dispute") - evidence_id: int = Field(description="ID of the evidence") - is_valid: bool = Field(description="Whether the evidence is valid") - verification_score: int = Field(description="Verification score (0-100)") - - -class VerifyEvidenceResponse(BaseModel): - """Response model for verifying evidence""" - success: bool - status: str - message: str - - -class SubmitArbitrationVoteRequest(BaseModel): - """Request model for submitting arbitration vote""" - dispute_id: int = Field(description="ID of the dispute") - vote_in_favor_of_initiator: bool = Field(description="Vote for initiator") - confidence: int = Field(description="Confidence level (0-100)") - reasoning: str = Field(description="Reasoning for the vote") - - -class SubmitArbitrationVoteResponse(BaseModel): - """Response model for submitting arbitration vote""" - success: bool - status: str - message: str - - -class AuthorizeArbitratorRequest(BaseModel): - """Request model for authorizing an arbitrator""" - arbitrator: str = Field(description="Address of the arbitrator") - reputation_score: int = Field(description="Initial reputation score") - - -class AuthorizeArbitratorResponse(BaseModel): - """Response model for authorizing an arbitrator""" - success: bool - status: str - message: str - - -class GetDisputeResponse(BaseModel): - """Response model for getting dispute details""" - dispute_id: int - agreement_id: int - initiator: str - respondent: str - status: str - dispute_type: str - reason: str - evidence_hash: str - filing_time: int - evidence_deadline: int - arbitration_deadline: int - resolution_amount: int - winner: str - resolution_reason: str - arbitrator_count: int - is_escalated: bool - escalation_level: int - - -class GetEvidenceResponse(BaseModel): - """Response model for getting dispute evidence""" - evidence_id: int - dispute_id: int - submitter: str - evidence_type: str - evidence_data: str - evidence_hash: str - submission_time: int - is_valid: bool - verification_score: int - verified_by: str - - -class GetArbitrationVotesResponse(BaseModel): - """Response model for getting arbitration votes""" - dispute_id: int - arbitrator: str - vote_in_favor_of_initiator: bool - confidence: int - reasoning: str - vote_time: int - is_valid: bool - - -@router.post("/disputes/file", summary="File a new dispute") -async def file_dispute( - request: FileDisputeRequest, - http_request: Request, - credentials: Optional[HTTPAuthorizationCredentials] = Depends(security) -) -> FileDisputeResponse: - """ - File a new dispute for a marketplace transaction. - This interacts with the DisputeResolution smart contract. - """ - try: - # Get authenticated address from request - sender_address = get_authenticated_address(http_request, credentials) - - # Use dispute resolution service - result = dispute_resolution_service.file_dispute( - agreement_id=request.agreement_id, - respondent=request.respondent, - dispute_type=request.dispute_type, - reason=request.reason, - evidence_hash=request.evidence_hash, - sender_address=sender_address - ) - - if not result.get("success"): - raise HTTPException(status_code=500, detail=result.get("error", "Failed to file dispute")) - - return FileDisputeResponse( - success=True, - dispute_id=result["dispute_id"], - status=result["status"], - message=result["message"] - ) - except HTTPException: - raise - except Exception as e: - _logger.error(f"Error filing dispute: {e}") - raise HTTPException(status_code=500, detail=f"Failed to file dispute: {str(e)}") - - -@router.post("/disputes/evidence", summary="Submit evidence for a dispute") -async def submit_evidence( - request: SubmitEvidenceRequest, - http_request: Request, - credentials: Optional[HTTPAuthorizationCredentials] = Depends(security) -) -> SubmitEvidenceResponse: - """ - Submit evidence for a dispute. - This interacts with the DisputeResolution smart contract. - """ - try: - # Get authenticated address from request - submitter_address = get_authenticated_address(http_request, credentials) - - result = dispute_resolution_service.submit_evidence( - dispute_id=request.dispute_id, - evidence_type=request.evidence_type, - evidence_data=request.evidence_data, - submitter_address=submitter_address - ) - - if not result.get("success"): - raise HTTPException(status_code=500, detail=result.get("error", "Failed to submit evidence")) - - return SubmitEvidenceResponse( - success=True, - evidence_id=result["evidence_id"], - status=result["status"], - message=result["message"] - ) - except HTTPException: - raise - except Exception as e: - _logger.error(f"Error submitting evidence: {e}") - raise HTTPException(status_code=500, detail=f"Failed to submit evidence: {str(e)}") - - -@router.post("/disputes/verify-evidence", summary="Verify evidence (arbitrator only)") -async def verify_evidence( - request: VerifyEvidenceRequest, - http_request: Request, - credentials: Optional[HTTPAuthorizationCredentials] = Depends(security) -) -> VerifyEvidenceResponse: - """ - Verify evidence submitted in a dispute. - This can only be called by authorized arbitrators. - """ - try: - # Get authenticated address from request - arbitrator_address = get_authenticated_address(http_request, credentials) - - result = dispute_resolution_service.verify_evidence( - dispute_id=request.dispute_id, - evidence_id=request.evidence_id, - is_valid=request.is_valid, - verification_score=request.verification_score, - arbitrator_address=arbitrator_address - ) - - if not result.get("success"): - raise HTTPException(status_code=500, detail=result.get("error", "Failed to verify evidence")) - - return VerifyEvidenceResponse( - success=True, - status=result["status"], - message=result["message"] - ) - except HTTPException: - raise - except Exception as e: - _logger.error(f"Error verifying evidence: {e}") - raise HTTPException(status_code=500, detail=f"Failed to verify evidence: {str(e)}") - - -@router.post("/disputes/vote", summary="Submit arbitration vote (arbitrator only)") -async def submit_arbitration_vote( - request: SubmitArbitrationVoteRequest, - http_request: Request, - credentials: Optional[HTTPAuthorizationCredentials] = Depends(security) -) -> SubmitArbitrationVoteResponse: - """ - Submit an arbitration vote for a dispute. - This can only be called by authorized arbitrators assigned to the dispute. - """ - try: - # Get authenticated address from request - arbitrator_address = get_authenticated_address(http_request, credentials) - - # Reject zero address in all modes - this is a sensitive arbitration operation - if arbitrator_address == "0x0000000000000000000000000000000000000000": - _logger.error("Vote submission attempted with zero address - rejected") - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Zero address is not allowed for arbitration operations" - ) - - return SubmitArbitrationVoteResponse( - success=True, - status="Submitted", - message=f"Vote submitted successfully for dispute {request.dispute_id}" - ) - except HTTPException: - raise - except Exception as e: - _logger.error(f"Error submitting arbitration vote: {e}") - raise HTTPException(status_code=500, detail=f"Failed to submit vote: {str(e)}") - - -@router.post("/disputes/arbitrators/authorize", summary="Authorize an arbitrator (admin only)") -async def authorize_arbitrator( - request: AuthorizeArbitratorRequest, - http_request: Request, - credentials: Optional[HTTPAuthorizationCredentials] = Depends(security) -) -> AuthorizeArbitratorResponse: - """ - Authorize a new arbitrator. - This can only be called by the contract owner. - """ - try: - # Get authenticated address from request - owner_address = get_authenticated_address(http_request, credentials) - - result = dispute_resolution_service.authorize_arbitrator( - arbitrator_address=request.arbitrator, - reputation_score=request.reputation_score, - owner_address=owner_address - ) - - if not result.get("success"): - raise HTTPException(status_code=500, detail=result.get("error", "Failed to authorize arbitrator")) - - return AuthorizeArbitratorResponse( - success=True, - status=result["status"], - message=result["message"] - ) - except HTTPException: - raise - except Exception as e: - _logger.error(f"Error authorizing arbitrator: {e}") - raise HTTPException(status_code=500, detail=f"Failed to authorize arbitrator: {str(e)}") - - -@router.get("/disputes/active", summary="Get all active disputes") -async def get_active_disputes() -> Dict[str, Any]: - """ - Get all active disputes. - This retrieves information from the DisputeResolution smart contract. - """ - try: - result = dispute_resolution_service.get_active_disputes() - - if not result.get("success"): - raise HTTPException(status_code=500, detail=result.get("error", "Failed to get active disputes")) - - return result - except HTTPException: - raise - except Exception as e: - _logger.error(f"Error getting active disputes: {e}") - raise HTTPException(status_code=500, detail=f"Failed to get active disputes: {str(e)}") - - -@router.get("/disputes/arbitrators", summary="Get all authorized arbitrators") -async def get_authorized_arbitrators() -> Dict[str, Any]: - """ - Get all authorized arbitrators. - This retrieves information from the DisputeResolution smart contract. - """ - try: - result = dispute_resolution_service.get_authorized_arbitrators() - - if not result.get("success"): - raise HTTPException(status_code=500, detail=result.get("error", "Failed to get authorized arbitrators")) - - return result - except HTTPException: - raise - except Exception as e: - _logger.error(f"Error getting authorized arbitrators: {e}") - raise HTTPException(status_code=500, detail=f"Failed to get authorized arbitrators: {str(e)}") - - -@router.get("/disputes/arbitrators/{arbitrator_address}", summary="Get disputes for an arbitrator") -async def get_arbitrator_disputes(arbitrator_address: str) -> Dict[str, Any]: - """ - Get all disputes assigned to an arbitrator. - This retrieves information from the DisputeResolution smart contract. - """ - try: - result = dispute_resolution_service.get_arbitrator_disputes(arbitrator_address) - - if not result.get("success"): - raise HTTPException(status_code=500, detail=result.get("error", "Failed to get arbitrator disputes")) - - return result - except HTTPException: - raise - except Exception as e: - _logger.error(f"Error getting arbitrator disputes: {e}") - raise HTTPException(status_code=500, detail=f"Failed to get arbitrator disputes: {str(e)}") - - -@router.get("/disputes/user/{user_address}", summary="Get disputes for a user") -async def get_user_disputes(user_address: str) -> Dict[str, Any]: - """ - Get all disputes for a specific user. - This retrieves information from the DisputeResolution smart contract. - """ - try: - result = dispute_resolution_service.get_user_disputes(user_address) - - if not result.get("success"): - raise HTTPException(status_code=500, detail=result.get("error", "Failed to get user disputes")) - - return result - except HTTPException: - raise - except Exception as e: - _logger.error(f"Error getting user disputes: {e}") - raise HTTPException(status_code=500, detail=f"Failed to get user disputes: {str(e)}") - - -@router.get("/disputes/{dispute_id}", summary="Get dispute details") -async def get_dispute(dispute_id: int) -> GetDisputeResponse: - """ - Get details of a specific dispute. - This retrieves information from the DisputeResolution smart contract. - """ - try: - result = dispute_resolution_service.get_dispute(dispute_id) - - if not result.get("success"): - raise HTTPException(status_code=404, detail=result.get("error", "Dispute not found")) - - dispute_data = result["dispute"] - return GetDisputeResponse(**dispute_data) - except HTTPException: - raise - except Exception as e: - _logger.error(f"Error getting dispute: {e}") - raise HTTPException(status_code=500, detail=f"Failed to get dispute: {str(e)}") - - -@router.get("/disputes/{dispute_id}/evidence", summary="Get evidence for a dispute") -async def get_dispute_evidence(dispute_id: int) -> List[GetEvidenceResponse]: - """ - Get all evidence submitted for a dispute. - This retrieves information from the DisputeResolution smart contract. - """ - try: - result = dispute_resolution_service.get_dispute_evidence(dispute_id) - - if not result.get("success"): - raise HTTPException(status_code=500, detail=result.get("error", "Failed to get dispute evidence")) - - return [GetEvidenceResponse(**e) for e in result["evidence"]] - except HTTPException: - raise - except Exception as e: - _logger.error(f"Error getting dispute evidence: {e}") - raise HTTPException(status_code=500, detail=f"Failed to get dispute evidence: {str(e)}") - - -@router.get("/disputes/{dispute_id}/votes", summary="Get arbitration votes for a dispute") -async def get_arbitration_votes(dispute_id: int) -> List[GetArbitrationVotesResponse]: - """ - Get all arbitration votes for a dispute. - This retrieves information from the DisputeResolution smart contract. - """ - try: - result = dispute_resolution_service.get_arbitration_votes(dispute_id) - - if not result.get("success"): - raise HTTPException(status_code=500, detail=result.get("error", "Failed to get arbitration votes")) - - return [GetArbitrationVotesResponse(**v) for v in result["votes"]] - except HTTPException: - raise - except Exception as e: - _logger.error(f"Error getting arbitration votes: {e}") - raise HTTPException(status_code=500, detail=f"Failed to get arbitration votes: {str(e)}") - - -@router.post("/islands/join", summary="Join an island") -async def join_island(request: JoinIslandRequest) -> JoinIslandResponse: - """ - Join an island for edge compute operations. - Calls IslandManager.join_island to register the node as a member of the specified island. - """ - island_manager = get_island_manager() - if island_manager is None: - raise HTTPException(status_code=503, detail="Island manager not available") - - success = island_manager.join_island( - island_id=request.island_id, - island_name=request.island_name, - chain_id=request.chain_id, - is_hub=request.is_hub - ) - - if success: - return JoinIslandResponse( - success=True, - island_id=request.island_id, - status="joined", - message=f"Successfully joined island {request.island_id}" - ) - else: - return JoinIslandResponse( - success=False, - island_id=request.island_id, - status="failed", - message=f"Failed to join island {request.island_id} (may already be a member)" - ) - - -@router.post("/islands/leave", summary="Leave an island") -async def leave_island(request: LeaveIslandRequest) -> LeaveIslandResponse: - """ - Leave an island. - Calls IslandManager.leave_island to remove the node from the specified island. - """ - island_manager = get_island_manager() - if island_manager is None: - raise HTTPException(status_code=503, detail="Island manager not available") - - success = island_manager.leave_island(request.island_id) - - if success: - return LeaveIslandResponse( - success=True, - island_id=request.island_id, - status="left", - message=f"Successfully left island {request.island_id}" - ) - else: - return LeaveIslandResponse( - success=False, - island_id=request.island_id, - status="failed", - message=f"Failed to leave island {request.island_id} (may not be a member)" - ) - - -@router.get("/islands", summary="List all islands") -@rate_limit(rate=100, per=60) -async def list_islands() -> Dict[str, Any]: - """ - List all islands that the node is a member of. - Calls IslandManager.get_all_islands to retrieve island memberships. - """ - island_manager = get_island_manager() - if island_manager is None: - raise HTTPException(status_code=503, detail="Island manager not available") - - islands = island_manager.get_all_islands() - - return { - "islands": [ - { - "island_id": island.island_id, - "island_name": island.island_name, - "chain_id": island.chain_id, - "status": island.status.value, - "role": getattr(island, 'role', 'unknown'), - "peer_count": island.peer_count, - "is_hub": island.is_hub, - "joined_at": island.joined_at - } - for island in islands - ], - "total": len(islands) - } - - -@router.get("/islands/{island_id}", summary="Get island details") -@rate_limit(rate=100, per=60) -async def get_island(island_id: str) -> Dict[str, Any]: - """ - Get details about a specific island. - Calls IslandManager.get_island_info to retrieve island membership details. - """ - island_manager = get_island_manager() - if island_manager is None: - raise HTTPException(status_code=503, detail="Island manager not available") - - island = island_manager.get_island_info(island_id) - - if island is None: - raise HTTPException(status_code=404, detail=f"Island {island_id} not found") - - return { - "island_id": island.island_id, - "island_name": island.island_name, - "chain_id": island.chain_id, - "status": island.status.value, - "role": getattr(island, 'role', 'unknown'), - "peer_count": island.peer_count, - "is_hub": island.is_hub, - "joined_at": island.joined_at - } - - -@router.post("/islands/bridge", summary="Request a bridge to another island") -async def request_bridge(request: BridgeRequestRequest) -> BridgeRequestResponse: - """ - Request a bridge to another island for cross-island communication. - Calls IslandManager.request_bridge to initiate a bridge request. - """ - island_manager = get_island_manager() - if island_manager is None: - raise HTTPException(status_code=503, detail="Island manager not available") - - request_id = island_manager.request_bridge(request.target_island_id) - - if request_id: - return BridgeRequestResponse( - success=True, - request_id=request_id, - target_island_id=request.target_island_id, - status="pending", - message=f"Bridge request {request_id} submitted for {request.target_island_id}" - ) - else: - return BridgeRequestResponse( - success=False, - request_id="", - target_island_id=request.target_island_id, - status="failed", - message=f"Failed to request bridge to {request.target_island_id} (may already be a member)" - ) - - -@router.get("/accounts/{address}", summary="Get account details") -@rate_limit(rate=200, per=60) -async def get_account( - request: Request, - address: str, - chain_id: str = None -) -> Dict[str, Any]: - """ - Get account details including balance and nonce. - - Args: - address: The account address - chain_id: Optional chain ID (defaults to node's chain) - - Returns: - Account details or 404 if not found - """ - chain_id = get_chain_id(chain_id) - address = address.lower().strip() - - with session_scope() as session: - account = session.get(Account, (chain_id, address)) - if not account: - raise HTTPException(status_code=404, detail=f"Account {address} not found on chain {chain_id}") - - return { - "success": True, - "address": account.address, - "chain_id": account.chain_id, - "balance": account.balance, - "nonce": account.nonce, - "updated_at": account.updated_at.isoformat() if account.updated_at else None - } - - -@router.post("/register-account", summary="Create/register a new account on the blockchain") -@rate_limit(rate=100, per=60) -async def create_account( - request: Request, - account_data: dict -) -> Dict[str, Any]: - """ - Create or register a new account on the blockchain. - - This endpoint allows wallets to register their public keys as accounts - on the blockchain, enabling them to send and receive transactions. - - Args: - account_data: Dictionary containing: - - address: The account address/public key (hex string) - - chain_id: Optional chain ID (defaults to node's chain) - - Returns: - Dictionary with success status and account details - """ - chain_id = get_chain_id(account_data.get("chain_id")) - address = account_data.get("address") - - if not address: - raise HTTPException(status_code=400, detail="address is required") - - # Normalize address (ensure lowercase hex) - address = address.lower().strip() - if not address.startswith("0x"): - address = "0x" + address - - # Validate address format (should be hex) - if not all(c in "0123456789abcdef" for c in address[2:]): - raise HTTPException(status_code=400, detail="address must be a valid hex string") - - with session_scope() as session: - # Check if account already exists - existing_account = session.get(Account, (chain_id, address)) - if existing_account: - return { - "success": True, - "address": address, - "chain_id": chain_id, - "balance": existing_account.balance, - "nonce": existing_account.nonce, - "created": False, - "message": "Account already exists" - } - - # Create new account with zero balance - new_account = Account( - chain_id=chain_id, - address=address, - balance=0, - nonce=0 - ) - session.add(new_account) - session.commit() - - _logger.info(f"Created new account: address={address}, chain_id={chain_id}") - - return { - "success": True, - "address": address, - "chain_id": chain_id, - "balance": 0, - "nonce": 0, - "created": True, - "message": "Account created successfully" - } - - -@router.post("/faucet", summary="Request test tokens from faucet") -@rate_limit(rate=10, per=3600) # 10 requests per hour per IP -async def faucet_request( - request: Request, - faucet_data: dict -) -> Dict[str, Any]: - """ - Request test tokens from the blockchain faucet. - - This endpoint allows newly created wallets to receive initial funds - for testing and development purposes. - - Args: - faucet_data: Dictionary containing: - - address: The account address to fund - - amount: Optional amount to request (default: 1000000) - - chain_id: Optional chain ID (defaults to node's chain) - - Returns: - Dictionary with success status and transaction details - """ - chain_id = get_chain_id(faucet_data.get("chain_id")) - address = faucet_data.get("address") - amount = faucet_data.get("amount", 1000000) # Default 1M units - - if not address: - raise HTTPException(status_code=400, detail="address is required") - - # Normalize address - address = address.lower().strip() - if not address.startswith("0x"): - address = "0x" + address - - # Validate address format - if not all(c in "0123456789abcdef" for c in address[2:]): - raise HTTPException(status_code=400, detail="address must be a valid hex string") - - # Cap max faucet amount - if amount > 10000000: # Max 10M per request - amount = 10000000 - - with session_scope() as session: - # Check if account exists - account = session.get(Account, (chain_id, address)) - if not account: - # Auto-create account if it doesn't exist - account = Account(chain_id=chain_id, address=address, balance=0, nonce=0) - session.add(account) - session.flush() - _logger.info(f"Faucet auto-created account: {address}") - - # Generate faucet transaction (special minting transaction) - timestamp = datetime.now(timezone.utc) - tx_hash = hashlib.sha256( - f"faucet:{address}:{amount}:{timestamp.isoformat()}:{uuid.uuid4()}".encode() - ).hexdigest() - - # Apply balance update directly (faucet is special system tx) - account.balance += amount - session.add(account) - - # Create faucet transaction record - faucet_tx = Transaction( - chain_id=chain_id, - tx_hash=tx_hash, - sender="faucet", - recipient=address, - payload={"type": "FAUCET", "amount": amount, "reason": "test_funding"}, - value=amount, - fee=0, - nonce=0, - timestamp=timestamp, - block_height=None, # Not in a block - direct system tx - status="confirmed", - type="FAUCET" - ) - session.add(faucet_tx) - session.commit() - - _logger.info(f"Faucet funded {address} with {amount} units on {chain_id}") - - return { - "success": True, - "tx_hash": tx_hash, - "address": address, - "amount": amount, - "chain_id": chain_id, - "new_balance": account.balance, - "message": f"Successfully funded {address} with {amount} units" - } - - -@router.post("/bridge/lock", summary="Lock funds for cross-chain transfer") -@rate_limit(rate=20, per=60) -async def bridge_lock( - request: Request, - lock_data: dict -) -> Dict[str, Any]: - """ - Initiate a cross-chain bridge transfer by locking funds. - - This is step 1 of the atomic bridge: - 1. Lock funds on source chain (this endpoint) - 2. Generate proof - 3. Confirm on target chain - """ - try: - from ..cross_chain.bridge import get_cross_chain_bridge - bridge = get_cross_chain_bridge() - - if not bridge: - raise HTTPException(status_code=503, detail="Cross-chain bridge not initialized") - - source_chain = lock_data.get("source_chain", get_chain_id(None)) - target_chain = lock_data.get("target_chain") - sender = lock_data.get("sender") - recipient = lock_data.get("recipient") - amount = lock_data.get("amount", 0) - asset = lock_data.get("asset", "native") - - if not all([target_chain, sender, recipient]): - raise HTTPException(status_code=400, detail="Missing required fields: target_chain, sender, recipient") - - if amount <= 0: - raise HTTPException(status_code=400, detail="Amount must be positive") - - # Execute lock - transfer = bridge.initiate_transfer( - source_chain=source_chain, - target_chain=target_chain, - sender=sender.lower(), - recipient=recipient.lower(), - amount=amount, - asset=asset - ) - - return { - "success": True, - "transfer_id": transfer.transfer_id, - "status": transfer.status.value, - "source_chain": source_chain, - "target_chain": target_chain, - "sender": sender, - "recipient": recipient, - "amount": amount, - "fee": (amount * 10) // 10000, # 0.1% fee - "lock_time": transfer.lock_time.isoformat() if transfer.lock_time else None, - "message": "Funds locked successfully. Use /bridge/confirm to complete." - } - - except ValueError as e: - raise HTTPException(status_code=400, detail=str(e)) - except Exception as e: - _logger.error(f"Bridge lock failed: {e}") - raise HTTPException(status_code=500, detail=f"Bridge lock failed: {str(e)}") - - -@router.post("/bridge/confirm", summary="Confirm and release cross-chain transfer") -@rate_limit(rate=20, per=60) -async def bridge_confirm( - request: Request, - confirm_data: dict -) -> Dict[str, Any]: - """ - Confirm a cross-chain bridge transfer and release funds. - - This is step 2 of the atomic bridge: - 1. Validate proof of lock - 2. Release funds on target chain - 3. Mark transfer as complete - """ - try: - from ..cross_chain.bridge import get_cross_chain_bridge - bridge = get_cross_chain_bridge() - - if not bridge: - raise HTTPException(status_code=503, detail="Cross-chain bridge not initialized") - - transfer_id = confirm_data.get("transfer_id") - proof = confirm_data.get("proof") - - if not transfer_id or not proof: - raise HTTPException(status_code=400, detail="Missing required fields: transfer_id, proof") - - # Execute confirmation - transfer = bridge.confirm_transfer(transfer_id, proof) - - return { - "success": True, - "transfer_id": transfer.transfer_id, - "status": transfer.status.value, - "source_chain": transfer.source_chain, - "target_chain": transfer.target_chain, - "sender": transfer.sender, - "recipient": transfer.recipient, - "amount": transfer.amount, - "target_tx_hash": transfer.target_tx_hash, - "confirm_time": transfer.confirm_time.isoformat() if transfer.confirm_time else None, - "message": "Cross-chain transfer completed successfully" - } - - except ValueError as e: - raise HTTPException(status_code=400, detail=str(e)) - except Exception as e: - _logger.error(f"Bridge confirm failed: {e}") - raise HTTPException(status_code=500, detail=f"Bridge confirm failed: {str(e)}") - - -@router.get("/bridge/transfer/{transfer_id}", summary="Get transfer status") -@rate_limit(rate=100, per=60) -async def get_bridge_transfer( - request: Request, - transfer_id: str -) -> Dict[str, Any]: - """Get the status of a cross-chain transfer""" - try: - from ..cross_chain.bridge import get_cross_chain_bridge - bridge = get_cross_chain_bridge() - - if not bridge: - raise HTTPException(status_code=503, detail="Cross-chain bridge not initialized") - - transfer = bridge.get_transfer(transfer_id) - if not transfer: - raise HTTPException(status_code=404, detail=f"Transfer {transfer_id} not found") - - return { - "success": True, - "transfer_id": transfer.transfer_id, - "status": transfer.status.value, - "source_chain": transfer.source_chain, - "target_chain": transfer.target_chain, - "sender": transfer.sender, - "recipient": transfer.recipient, - "amount": transfer.amount, - "asset": transfer.asset, - "source_tx_hash": transfer.source_tx_hash, - "target_tx_hash": transfer.target_tx_hash, - "lock_time": transfer.lock_time.isoformat() if transfer.lock_time else None, - "confirm_time": transfer.confirm_time.isoformat() if transfer.confirm_time else None - } - - except HTTPException: - raise - except Exception as e: - _logger.error(f"Get bridge transfer failed: {e}") - raise HTTPException(status_code=500, detail=f"Failed to get transfer: {str(e)}") - - -@router.get("/bridge/pending", summary="List pending bridge transfers") -@rate_limit(rate=50, per=60) -async def list_pending_transfers( - request: Request, - chain_id: str = None -) -> List[Dict[str, Any]]: - """List all pending cross-chain transfers""" - try: - from ..cross_chain.bridge import get_cross_chain_bridge - bridge = get_cross_chain_bridge() - - if not bridge: - raise HTTPException(status_code=503, detail="Cross-chain bridge not initialized") - - chain_id = get_chain_id(chain_id) - transfers = bridge.list_pending_transfers(chain_id) - - return [ - { - "transfer_id": t.transfer_id, - "source_chain": t.source_chain, - "target_chain": t.target_chain, - "sender": t.sender, - "recipient": t.recipient, - "amount": t.amount, - "status": t.status.value, - "lock_time": t.lock_time.isoformat() if t.lock_time else None - } - for t in transfers - ] - - except Exception as e: - _logger.error(f"List pending transfers failed: {e}") - raise HTTPException(status_code=500, detail=f"Failed to list transfers: {str(e)}") - - -@router.post("/staking/stake", summary="Stake tokens") -@rate_limit(rate=20, per=60) -async def stake_tokens( - request: Request, - stake_data: dict -) -> Dict[str, Any]: - """ - Stake tokens for consensus participation. - - Locks tokens for a specified period. Staked tokens earn rewards - and provide voting power in consensus. - """ - chain_id = get_chain_id(stake_data.get("chain_id")) - address = stake_data.get("address") - amount = stake_data.get("amount", 0) - lock_days = stake_data.get("lock_days", 30) - - if not address: - raise HTTPException(status_code=400, detail="address is required") - - if amount <= 0: - raise HTTPException(status_code=400, detail="amount must be positive") - - # Normalize address - address = address.lower().strip() - if not address.startswith("0x"): - address = "0x" + address - - with session_scope() as session: - # Get account - account = session.get(Account, (chain_id, address)) - if not account: - raise HTTPException(status_code=404, detail=f"Account {address} not found") - - if account.balance < amount: - raise HTTPException( - status_code=400, - detail=f"Insufficient balance: {account.balance} < {amount}" - ) - - # Lock tokens (deduct from balance) - account.balance -= amount - session.add(account) - - # Calculate lock period - locked_until = datetime.now(timezone.utc) - locked_until = locked_until.replace(day=locked_until.day + lock_days) - - # Create stake record - stake = Stake( - chain_id=chain_id, - address=address, - amount=amount, - locked_until=locked_until, - status="active" - ) - session.add(stake) - session.commit() - - _logger.info(f"Tokens staked: {address} staked {amount} on {chain_id}") - - return { - "success": True, - "stake_id": stake.id, - "address": address, - "amount": amount, - "chain_id": chain_id, - "locked_until": locked_until.isoformat(), - "status": "active", - "remaining_balance": account.balance - } - - -@router.post("/staking/unstake", summary="Unstake tokens") -@rate_limit(rate=10, per=60) -async def unstake_tokens( - request: Request, - unstake_data: dict -) -> Dict[str, Any]: - """ - Unstake tokens after lock period expires. - - Returns staked tokens to account balance. - """ - chain_id = get_chain_id(unstake_data.get("chain_id")) - address = unstake_data.get("address") - stake_id = unstake_data.get("stake_id") - - if not address or not stake_id: - raise HTTPException(status_code=400, detail="address and stake_id are required") - - # Normalize address - address = address.lower().strip() - if not address.startswith("0x"): - address = "0x" + address - - with session_scope() as session: - # Get stake record - stake = session.get(Stake, stake_id) - if not stake: - raise HTTPException(status_code=404, detail=f"Stake {stake_id} not found") - - if stake.address != address: - raise HTTPException(status_code=403, detail="Not authorized to unstake") - - if stake.status != "active": - raise HTTPException(status_code=400, detail=f"Stake is not active: {stake.status}") - - # Check if lock period expired - now = datetime.now(timezone.utc) - if stake.locked_until and now < stake.locked_until: - raise HTTPException( - status_code=400, - detail=f"Lock period not expired. Locked until: {stake.locked_until.isoformat()}" - ) - - # Return tokens to account - account = session.get(Account, (chain_id, address)) - if not account: - # Account was deleted, recreate - account = Account(chain_id=chain_id, address=address, balance=0, nonce=0) - session.add(account) - - account.balance += stake.amount - session.add(account) - - # Update stake status - stake.status = "withdrawn" - session.add(stake) - session.commit() - - _logger.info(f"Tokens unstaked: {address} recovered {stake.amount} from stake {stake_id}") - - return { - "success": True, - "stake_id": stake_id, - "address": address, - "amount": stake.amount, - "chain_id": chain_id, - "new_balance": account.balance, - "status": "withdrawn" - } - - -@router.get("/staking/{address}", summary="Get staking info") -@rate_limit(rate=100, per=60) -async def get_staking_info( - request: Request, - address: str, - chain_id: str = None -) -> Dict[str, Any]: - """Get staking information for an address""" - chain_id = get_chain_id(chain_id) - address = address.lower().strip() - - with session_scope() as session: - from sqlalchemy import select, func - - # Get all stakes for address - statement = select(Stake).where( - Stake.chain_id == chain_id, - Stake.address == address - ) - stakes = session.exec(statement).all() - - total_staked = sum(s.amount for s in stakes if s.status == "active") - active_stakes = [ - { - "stake_id": s.id, - "amount": s.amount, - "locked_until": s.locked_until.isoformat() if s.locked_until else None, - "status": s.status, - "created_at": s.created_at.isoformat() if s.created_at else None - } - for s in stakes if s.status == "active" - ] - - return { - "success": True, - "address": address, - "chain_id": chain_id, - "total_staked": total_staked, - "active_stake_count": len(active_stakes), - "active_stakes": active_stakes - } - - -@router.get("/balance/{address}", summary="Get detailed balance breakdown") -@rate_limit(rate=100, per=60) -async def get_balance_breakdown( - request: Request, - address: str, - chain_id: str = None -) -> Dict[str, Any]: - """ - Get detailed balance breakdown including: - - Available balance - - Staked amount - - Bridge-locked amount - - Total balance - """ - try: - from ..services.balance_tracker import get_balance_tracker - tracker = get_balance_tracker() - - if not tracker: - raise HTTPException(status_code=503, detail="Balance tracker not initialized") - - chain_id = get_chain_id(chain_id) - address = address.lower().strip() - - breakdown = tracker.get_balance_breakdown(address, chain_id) - return breakdown - - except HTTPException: - raise - except Exception as e: - _logger.error(f"Failed to get balance breakdown: {e}") - raise HTTPException(status_code=500, detail=f"Failed to get balance: {str(e)}") - - -@router.get("/balance/{address}/reconcile", summary="Reconcile balance") -@rate_limit(rate=20, per=60) -async def reconcile_balance( - request: Request, - address: str, - chain_id: str = None -) -> Dict[str, Any]: - """ - Reconcile account balance against all recorded operations. - - Verifies that current balance matches expected balance - based on all transactions, stakes, and bridge operations. - """ - try: - from ..services.balance_tracker import get_balance_tracker - tracker = get_balance_tracker() - - if not tracker: - raise HTTPException(status_code=503, detail="Balance tracker not initialized") - - chain_id = get_chain_id(chain_id) - address = address.lower().strip() - - result = tracker.reconcile_balance(address, chain_id) - return result - - except HTTPException: - raise - except Exception as e: - _logger.error(f"Balance reconciliation failed: {e}") - raise HTTPException(status_code=500, detail=f"Reconciliation failed: {str(e)}") diff --git a/cli/aitbc_cli.legacy.py b/cli/aitbc_cli.legacy.py deleted file mode 100755 index b77a514e..00000000 --- a/cli/aitbc_cli.legacy.py +++ /dev/null @@ -1,3256 +0,0 @@ -#!/usr/bin/env python3 -""" -AITBC CLI - Comprehensive Blockchain Management Tool -""" -import sys -from pathlib import Path - -# Add /opt/aitbc to Python path for shared modules -sys.path.insert(0, str(Path("/opt/aitbc"))) - -""" -Complete command-line interface for AITBC blockchain operations including: -- Wallet management -- Transaction processing -- Blockchain analytics -- Marketplace operations -- AI compute jobs -- Mining operations -- Network monitoring -""" - -import json -import sys -import os -import time -import datetime -import argparse -import random -import hashlib -import httpx -import subprocess -from pathlib import Path -from cryptography.hazmat.primitives.asymmetric import ed25519 -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.ciphers.aead import AESGCM -from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.backends import default_backend -import requests -from typing import Optional, Dict, Any, List - -# Import shared modules -from aitbc.aitbc_logging import get_logger -from aitbc import __version__ -from aitbc.constants import BLOCKCHAIN_RPC_PORT, DATA_DIR, KEYSTORE_DIR -from aitbc.exceptions import ConfigurationError, NetworkError, ValidationError -from aitbc.network.http_client import AITBCHTTPClient -from aitbc.utils.paths import get_blockchain_data_path, get_data_path -from aitbc.utils.paths import ensure_dir, get_keystore_path -from aitbc.utils.validation import validate_address, validate_url - -# Initialize logger -logger = get_logger(__name__) - -# Default paths -CLI_VERSION = __version__ -DEFAULT_KEYSTORE_DIR = KEYSTORE_DIR -DEFAULT_RPC_URL = f"http://localhost:{BLOCKCHAIN_RPC_PORT}" -DEFAULT_WALLET_DAEMON_URL = "http://localhost:8003" - -def decrypt_private_key(keystore_path: Path, password: str) -> str: - """Decrypt private key from keystore file. - - Supports both keystore formats: - - AES-256-GCM (blockchain-node standard) - - Fernet (scripts/utils standard) - """ - with open(keystore_path) as f: - ks = json.load(f) - - crypto = ks.get('crypto', ks) # Handle both nested and flat crypto structures - - # Detect encryption method - cipher = crypto.get('cipher', crypto.get('algorithm', '')) - - if cipher == 'aes-256-gcm' or cipher == 'aes-256-gcm': - # AES-256-GCM (blockchain-node standard) - salt = bytes.fromhex(crypto['kdfparams']['salt']) - kdf = PBKDF2HMAC( - algorithm=hashes.SHA256(), - length=32, - salt=salt, - iterations=crypto['kdfparams']['c'], - backend=default_backend() - ) - key = kdf.derive(password.encode()) - aesgcm = AESGCM(key) - nonce = bytes.fromhex(crypto['cipherparams']['nonce']) - priv = aesgcm.decrypt(nonce, bytes.fromhex(crypto['ciphertext']), None) - return priv.hex() - - elif cipher == 'fernet' or cipher == 'PBKDF2-SHA256-Fernet': - # Fernet (scripts/utils standard) - from cryptography.fernet import Fernet - import base64 - - # Derive Fernet key using the same method as scripts/utils/keystore.py - kdfparams = crypto.get('kdfparams', {}) - if 'salt' in kdfparams: - salt = base64.b64decode(kdfparams['salt']) - else: - # Fallback for older format - salt = bytes.fromhex(kdfparams.get('salt', '')) - - # Use PBKDF2 for secure key derivation (100,000 iterations for security) - dk = hashlib.pbkdf2_hmac('sha256', password.encode(), salt, 100000, dklen=32) - fernet_key = base64.urlsafe_b64encode(dk) - - f = Fernet(fernet_key) - ciphertext = base64.b64decode(crypto['ciphertext']) - priv = f.decrypt(ciphertext) - return priv.decode() - - else: - raise ValueError(f"Unsupported cipher: {cipher}") - - -def create_wallet(name: str, password: str, keystore_dir: Path = DEFAULT_KEYSTORE_DIR) -> str: - """Create a new wallet using blockchain-node standard AES-256-GCM encryption""" - keystore_dir.mkdir(parents=True, exist_ok=True) - - # Generate new key pair - private_key = ed25519.Ed25519PrivateKey.generate() - private_key_hex = private_key.private_bytes_raw().hex() - public_key = private_key.public_key() - public_key_hex = public_key.public_bytes_raw().hex() - - # Calculate address (simplified - in real implementation this would be more complex) - address = f"ait1{public_key_hex[:40]}" - - # Encrypt private key using blockchain-node standard (AES-256-GCM with PBKDF2) - salt = os.urandom(32) - kdf = PBKDF2HMAC( - algorithm=hashes.SHA256(), - length=32, - salt=salt, - iterations=100_000, - backend=default_backend() - ) - key = kdf.derive(password.encode()) - aesgcm = AESGCM(key) - nonce = os.urandom(12) - ciphertext = aesgcm.encrypt(nonce, bytes.fromhex(private_key_hex), None) - - # Create keystore file matching blockchain-node format - keystore_data = { - "address": address, - "public_key": public_key_hex, - "crypto": { - "kdf": "pbkdf2", - "kdfparams": { - "salt": salt.hex(), - "c": 100_000, - "dklen": 32, - "prf": "hmac-sha256" - }, - "cipher": "aes-256-gcm", - "cipherparams": { - "nonce": nonce.hex() - }, - "ciphertext": ciphertext.hex() - }, - "keytype": "ed25519", - "version": 1 - } - - keystore_path = keystore_dir / f"{name}.json" - with open(keystore_path, 'w') as f: - json.dump(keystore_data, f, indent=2) - - print(f"Wallet created: {name}") - print(f"Address: {address}") - print(f"Keystore: {keystore_path}") - - return address - - -def send_transaction(from_wallet: str, to_address: str, amount: float, fee: float, - password: str, keystore_dir: Path = DEFAULT_KEYSTORE_DIR, - rpc_url: str = DEFAULT_RPC_URL) -> Optional[str]: - """Send transaction from one wallet to another""" - - # Validate recipient address - try: - validate_address(to_address) - except ValidationError as e: - logger.error(f"Invalid recipient address: {e}") - print(f"Error: Invalid recipient address: {e}") - return None - - # Validate amount - if amount <= 0: - logger.error(f"Invalid amount: {amount} must be positive") - print("Error: Amount must be positive") - return None - - # Ensure keystore_dir is a Path object - if keystore_dir is None: - keystore_dir = DEFAULT_KEYSTORE_DIR - if isinstance(keystore_dir, str): - keystore_dir = Path(keystore_dir) - - # Get sender wallet info - sender_keystore = keystore_dir / f"{from_wallet}.json" - if not sender_keystore.exists(): - print(f"Error: Wallet '{from_wallet}' not found") - return None - - with open(sender_keystore) as f: - sender_data = json.load(f) - - sender_address = sender_data['address'] - - # Decrypt private key - try: - private_key_hex = decrypt_private_key(sender_keystore, password) - private_key = ed25519.Ed25519PrivateKey.from_private_bytes(bytes.fromhex(private_key_hex)) - except Exception as e: - print(f"Error decrypting wallet: {e}") - return None - - # Get chain_id from RPC health endpoint or use override - from aitbc_cli.utils.chain_id import get_chain_id, get_default_chain_id - chain_id = get_chain_id(rpc_url, override=None, timeout=5) - - # Get actual nonce from blockchain - actual_nonce = 0 - try: - http_client = AITBCHTTPClient(base_url=rpc_url, timeout=5) - account_data = http_client.get(f"/rpc/account/{sender_address}") - actual_nonce = account_data.get("nonce", 0) - except NetworkError: - actual_nonce = 0 - except Exception: - actual_nonce = 0 - - # Create transaction - transaction = { - "type": "TRANSFER", - "chain_id": chain_id, - "from": sender_address, - "nonce": actual_nonce, - "fee": int(fee), - "payload": { - "recipient": to_address, - "amount": int(amount) - } - } - - # Sign transaction - message = json.dumps(transaction, sort_keys=True).encode() - signature = private_key.sign(message) - transaction["signature"] = signature.hex() - - # Submit to blockchain - try: - http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) - result = http_client.post("/rpc/transaction", json=transaction) - tx_hash = result.get("transaction_hash") - print(f"Transaction submitted: {tx_hash}") - logger.info(f"Transaction submitted: {tx_hash} from {from_wallet} to {to_address}") - return tx_hash - except NetworkError as e: - logger.error(f"Network error submitting transaction: {e}") - print(f"Error submitting transaction: {e}") - return None - except Exception as e: - logger.error(f"Error submitting transaction: {e}") - print(f"Error: {e}") - return None - - -def import_wallet(wallet_name: str, private_key_hex: str, password: str, - keystore_dir: Path = KEYSTORE_DIR) -> Optional[str]: - """Import wallet from private key""" - try: - ensure_dir(keystore_dir) - - # Validate and convert private key - try: - private_key_bytes = bytes.fromhex(private_key_hex) - private_key = ed25519.Ed25519PrivateKey.from_private_bytes(private_key_bytes) - except Exception as e: - print(f"Error: Invalid private key: {e}") - return None - - # Generate public key and address - public_key = private_key.public_key() - public_key_hex = public_key.public_bytes_raw().hex() - address = f"ait1{public_key_hex[:40]}" - - # Encrypt private key - salt = os.urandom(32) - kdf = PBKDF2HMAC(hashes.SHA256(), 32, salt, 100000) - key = kdf.derive(password.encode()) - aesgcm = AESGCM(key) - nonce = os.urandom(12) - ciphertext = aesgcm.encrypt(nonce, private_key_bytes, None) - - # Create keystore file - keystore_data = { - "address": address, - "public_key": public_key_hex, - "crypto": { - "kdf": "pbkdf2", - "kdfparams": { - "salt": salt.hex(), - "c": 100000, - "dklen": 32, - "prf": "hmac-sha256" - }, - "cipher": "aes-256-gcm", - "cipherparams": { - "nonce": nonce.hex() - }, - "ciphertext": ciphertext.hex() - }, - "version": 1 - } - - keystore_path = keystore_dir / f"{wallet_name}.json" - with open(keystore_path, 'w') as f: - json.dump(keystore_data, f, indent=2) - - print(f"Wallet imported: {wallet_name}") - print(f"Address: {address}") - logger.info(f"Imported wallet: {wallet_name} with address {address}") - print(f"Keystore: {keystore_path}") - - return address - except Exception as e: - print(f"Error importing wallet: {e}") - return None - - -def export_wallet(wallet_name: str, password: str, keystore_dir: Path = DEFAULT_KEYSTORE_DIR) -> Optional[str]: - """Export private key from wallet""" - try: - keystore_path = keystore_dir / f"{wallet_name}.json" - if not keystore_path.exists(): - print(f"Error: Wallet '{wallet_name}' not found") - return None - - return decrypt_private_key(keystore_path, password) - except Exception as e: - print(f"Error exporting wallet: {e}") - return None - - -def delete_wallet(wallet_name: str, keystore_dir: Path = DEFAULT_KEYSTORE_DIR) -> bool: - """Delete wallet""" - try: - keystore_path = keystore_dir / f"{wallet_name}.json" - if keystore_path.exists(): - keystore_path.unlink() - print(f"Wallet '{wallet_name}' deleted successfully") - return True - else: - print(f"Error: Wallet '{wallet_name}' not found") - return False - except Exception as e: - print(f"Error deleting wallet: {e}") - return False - - -def rename_wallet(old_name: str, new_name: str, keystore_dir: Path = DEFAULT_KEYSTORE_DIR) -> bool: - """Rename wallet""" - try: - old_path = keystore_dir / f"{old_name}.json" - new_path = keystore_dir / f"{new_name}.json" - - if not old_path.exists(): - print(f"Error: Wallet '{old_name}' not found") - return False - - if new_path.exists(): - print(f"Error: Wallet '{new_name}' already exists") - return False - - old_path.rename(new_path) - print(f"Wallet renamed from '{old_name}' to '{new_name}'") - return True - except Exception as e: - print(f"Error renaming wallet: {e}") - return False - - -def list_wallets(keystore_dir: Path = KEYSTORE_DIR, - use_daemon: bool = True, - daemon_url: str = DEFAULT_WALLET_DAEMON_URL) -> list: - """List all wallets""" - wallets = [] - - # Try to use wallet daemon first - if use_daemon: - try: - http_client = AITBCHTTPClient(base_url=daemon_url, timeout=5) - data = http_client.get("/v1/wallets") - wallet_list = data.get("items", data.get("wallets", [])) - for wallet_data in wallet_list: - wallets.append({ - "name": wallet_data.get("wallet_name", ""), - "address": wallet_data.get("address", ""), - "public_key": wallet_data.get("public_key", ""), - "source": "daemon" - }) - logger.info(f"Listed {len(wallets)} wallets from daemon") - return wallets - except NetworkError as e: - logger.warning(f"Failed to query wallet daemon: {e}, falling back to file-based listing") - print(f"Warning: Failed to query wallet daemon: {e}") - print("Falling back to file-based wallet listing...") - except Exception as e: - logger.warning(f"Failed to query wallet daemon: {e}, falling back to file-based listing") - print(f"Warning: Failed to query wallet daemon: {e}") - print("Falling back to file-based wallet listing...") - - # Fallback to file-based wallet listing - if keystore_dir.exists(): - for wallet_file in keystore_dir.glob("*.json"): - try: - with open(wallet_file) as f: - data = json.load(f) - wallets.append({ - "name": wallet_file.stem, - "address": data["address"], - "file": str(wallet_file), - "source": "file" - }) - except Exception: - pass - logger.info(f"Listed {len(wallets)} wallets from file-based fallback") - return wallets - - -def send_batch_transactions(transactions: List[Dict[str, Any]], password: str, - rpc_url: str = DEFAULT_RPC_URL) -> List[Optional[str]]: - """Send multiple transactions in batch""" - results = [] - - for tx in transactions: - try: - tx_hash = send_transaction( - tx['from_wallet'], - tx['to_address'], - tx['amount'], - tx.get('fee', 10.0), - password, - rpc_url - ) - results.append({ - 'transaction': tx, - 'hash': tx_hash, - 'success': tx_hash is not None - }) - - if tx_hash: - print(f"✅ Transaction sent: {tx['from_wallet']} → {tx['to_address']} ({tx['amount']} AIT)") - else: - print(f"❌ Transaction failed: {tx['from_wallet']} → {tx['to_address']}") - - except Exception as e: - results.append({ - 'transaction': tx, - 'hash': None, - 'success': False, - 'error': str(e) - }) - print(f"❌ Transaction error: {e}") - - return results - - -def estimate_transaction_fee(from_wallet: str, to_address: str, amount: float, - keystore_dir: Path = DEFAULT_KEYSTORE_DIR, - rpc_url: str = DEFAULT_RPC_URL) -> Optional[float]: - """Estimate transaction fee""" - try: - # Create a test transaction to estimate fee - test_tx = { - "sender": "", # Will be filled by actual sender - "recipient": to_address, - "value": int(amount), - "fee": 10, # Default fee - "nonce": 0, - "type": "transfer", - "payload": {} - } - - # Get fee estimation from RPC (if available) - try: - http_client = AITBCHTTPClient(base_url=rpc_url, timeout=10) - fee_data = http_client.post("/rpc/estimateFee", json=test_tx) - return fee_data.get("estimated_fee", 10.0) - except NetworkError: - # Fallback to default fee - return 10.0 - except Exception as e: - print(f"Error estimating fee: {e}") - return 10.0 - - -def get_transaction_status(tx_hash: str, rpc_url: str = DEFAULT_RPC_URL) -> Optional[Dict]: - """Get detailed transaction status""" - try: - http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) - return http_client.get(f"/rpc/transaction/{tx_hash}") - except NetworkError as e: - print(f"Error getting transaction status: {e}") - return None - - -def get_pending_transactions(rpc_url: str = DEFAULT_RPC_URL) -> List[Dict]: - """Get pending transactions in mempool""" - try: - http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) - data = http_client.get("/rpc/pending") - return data.get("transactions", []) - except NetworkError as e: - print(f"Error getting pending transactions: {e}") - return [] - - -def start_mining(wallet_name: str, threads: int = 1, keystore_dir: Path = DEFAULT_KEYSTORE_DIR, - rpc_url: str = DEFAULT_RPC_URL) -> bool: - """Start mining with specified wallet""" - try: - # Get wallet address - keystore_path = keystore_dir / f"{wallet_name}.json" - if not keystore_path.exists(): - print(f"Error: Wallet '{wallet_name}' not found") - return False - - with open(keystore_path) as f: - wallet_data = json.load(f) - address = wallet_data['address'] - - # Start mining via RPC - mining_config = { - "miner_address": address, - "threads": threads, - "enabled": True - } - - try: - http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) - result = http_client.post("/rpc/mining/start", json=mining_config) - print(f"Mining started with wallet '{wallet_name}'") - print(f"Miner address: {address}") - print(f"Threads: {threads}") - print(f"Status: {result.get('status', 'started')}") - return result - except NetworkError as e: - print(f"Error starting mining: {e}") - return None - except Exception as e: - print(f"Error: {e}") - return False - except Exception as e: - print(f"Error: {e}") - return False - - -def stop_mining(rpc_url: str = DEFAULT_RPC_URL) -> bool: - """Stop mining""" - try: - http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) - result = http_client.post("/rpc/mining/stop") - print(f"Mining stopped") - print(f"Status: {result.get('status', 'stopped')}") - return True - except NetworkError as e: - print(f"Error stopping mining: {e}") - return False - except Exception as e: - print(f"Error: {e}") - return False - - -def get_mining_status(rpc_url: str = DEFAULT_RPC_URL) -> Optional[Dict]: - """Get mining status and statistics""" - try: - http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) - return http_client.get("/rpc/mining/status") - except NetworkError as e: - print(f"Error getting mining status: {e}") - return None - - -def get_marketplace_listings(rpc_url: str = DEFAULT_RPC_URL) -> List[Dict]: - """Get marketplace listings""" - try: - http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) - data = http_client.get("/rpc/marketplace/listings") - return data.get("listings", []) - except NetworkError as e: - print(f"Error getting marketplace listings: {e}") - return [] - except Exception as e: - print(f"Error: {e}") - return [] - - -def create_marketplace_listing(wallet_name: str, item_type: str, price: float, - description: str, password: str, - keystore_dir: Path = DEFAULT_KEYSTORE_DIR, - rpc_url: str = DEFAULT_RPC_URL) -> Optional[str]: - """Create marketplace listing""" - try: - # Get wallet address - keystore_path = keystore_dir / f"{wallet_name}.json" - if not keystore_path.exists(): - print(f"Error: Wallet '{wallet_name}' not found") - return None - - with open(keystore_path) as f: - wallet_data = json.load(f) - address = wallet_data['address'] - - # Create listing - listing_data = { - "seller_address": address, - "item_type": item_type, - "price": price, - "description": description - } - - try: - http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) - result = http_client.post("/rpc/marketplace/create", json=listing_data) - listing_id = result.get("listing_id") - print(f"Marketplace listing created") - print(f"Listing ID: {listing_id}") - return result - except NetworkError as e: - print(f"Error creating marketplace listing: {e}") - return None - except Exception as e: - print(f"Error: {e}") - return None - - -def submit_ai_job(wallet_name: str, job_type: str, prompt: str, payment: float, - password: str, keystore_dir: Path = DEFAULT_KEYSTORE_DIR, - rpc_url: str = DEFAULT_RPC_URL) -> Optional[str]: - """Submit AI compute job""" - try: - # Get wallet address - keystore_path = keystore_dir / f"{wallet_name}.json" - if not keystore_path.exists(): - print(f"Error: Wallet '{wallet_name}' not found") - return None - - with open(keystore_path) as f: - wallet_data = json.load(f) - address = wallet_data['address'] - - # Submit job - job_data = { - "client_address": address, - "job_type": job_type, - "prompt": prompt, - "payment": payment - } - - try: - http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) - result = http_client.post("/rpc/ai/submit", json=job_data) - job_id = result.get("job_id") - print(f"AI job submitted") - print(f"Job ID: {job_id}") - print(f"Type: {job_type}") - print(f"Payment: {payment} AIT") - return job_id - except NetworkError as e: - print(f"Error submitting AI job: {e}") - return None - except Exception as e: - print(f"Error: {e}") - return None - except Exception as e: - print(f"Error: {e}") - return None - - -def get_balance(wallet_name: str, keystore_dir: Path = DEFAULT_KEYSTORE_DIR, - rpc_url: str = DEFAULT_RPC_URL) -> Optional[Dict]: - """Get wallet balance and transaction info""" - try: - keystore_path = keystore_dir / f"{wallet_name}.json" - if not keystore_path.exists(): - print(f"Error: Wallet '{wallet_name}' not found") - return None - - with open(keystore_path) as f: - wallet_data = json.load(f) - - address = wallet_data['address'] - - # Get balance from RPC - try: - http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) - balance_data = http_client.get(f"/rpc/getBalance/{address}") - return { - "address": address, - "balance": balance_data.get("balance", 0), - "nonce": balance_data.get("nonce", 0), - "wallet_name": wallet_name - } - except NetworkError as e: - print(f"Error getting balance: {e}") - return None - except Exception as e: - print(f"Error: {e}") - return None - - -def get_transactions(wallet_name: str, keystore_dir: Path = DEFAULT_KEYSTORE_DIR, - rpc_url: str = DEFAULT_RPC_URL, limit: int = 10) -> List[Dict]: - """Get wallet transaction history""" - try: - keystore_path = keystore_dir / f"{wallet_name}.json" - if not keystore_path.exists(): - print(f"Error: Wallet '{wallet_name}' not found") - return [] - - with open(keystore_path) as f: - wallet_data = json.load(f) - - address = wallet_data['address'] - - # Get transactions from RPC - try: - http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) - tx_data = http_client.get(f"/rpc/transactions?address={address}&limit={limit}") - if isinstance(tx_data, list): - transactions = tx_data - else: - transactions = tx_data.get("transactions", []) - wallet_transactions = [] - for tx in transactions: - if not isinstance(tx, dict): - continue - payload = tx.get("payload") if isinstance(tx.get("payload"), dict) else {} - if ( - tx.get("sender") == address - or tx.get("recipient") == address - or payload.get("from") == address - or payload.get("to") == address - or payload.get("recipient") == address - ): - normalized_tx = dict(tx) - if "hash" not in normalized_tx and "tx_hash" in normalized_tx: - normalized_tx["hash"] = normalized_tx["tx_hash"] - wallet_transactions.append(normalized_tx) - return wallet_transactions - except NetworkError as e: - print(f"Error getting transactions: {e}") - return [] - except Exception as e: - print(f"Error: {e}") - return [] - except Exception as e: - print(f"Error: {e}") - return [] - - -def get_balance(wallet_name: str, rpc_url: str = DEFAULT_RPC_URL, chain_id_override: str = None) -> Optional[Dict]: - """Get wallet balance""" - try: - # Get chain_id from RPC health endpoint or use override - from aitbc_cli.utils.chain_id import get_chain_id - chain_id = get_chain_id(rpc_url, override=chain_id_override, timeout=5) - - # 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 - try: - response = requests.get( - f"{rpc_url.rstrip('/')}/rpc/account/{address}", - params={"chain_id": chain_id}, - timeout=10, - ) - if response.status_code == 404: - return { - "wallet_name": wallet_name, - "address": address, - "balance": 0, - "nonce": 0 - } - response.raise_for_status() - account_info = response.json() - return { - "wallet_name": wallet_name, - "address": address, - "balance": account_info["balance"], - "nonce": account_info["nonce"] - } - except requests.RequestException as e: - print(f"Error getting balance: {e}") - return None - except Exception as e: - print(f"Error: {e}") - return None - except Exception as e: - print(f"Error: {e}") - return None - - -def get_chain_info(rpc_url: str = DEFAULT_RPC_URL) -> Optional[Dict]: - """Get blockchain information""" - try: - result = {} - # Get chain metadata from health endpoint - http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) - health = http_client.get("/health") - chains = health.get('supported_chains', []) - result['chain_id'] = chains[0] if chains else 'ait-mainnet' - result['supported_chains'] = ', '.join(chains) if chains else 'ait-mainnet' - result['proposer_id'] = health.get('proposer_id', '') - # Get head block for height - head = http_client.get("/rpc/head") - result['height'] = head.get('height', 0) - result['hash'] = head.get('hash', "") - result['timestamp'] = head.get('timestamp', 'N/A') - result['tx_count'] = head.get('tx_count', 0) - return result if result else None - except NetworkError as e: - print(f"Error: {e}") - return None - except Exception as e: - print(f"Error: {e}") - return None - - -def get_network_status(rpc_url: str = DEFAULT_RPC_URL) -> Optional[Dict]: - """Get network status and health""" - try: - # Get head block - http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) - return http_client.get("/rpc/head") - except NetworkError as e: - print(f"Error getting network status: {e}") - return None - except Exception as e: - print(f"Error: {e}") - return None - - -def get_blockchain_analytics(analytics_type: str, limit: int = 10, rpc_url: str = DEFAULT_RPC_URL) -> Optional[Dict]: - """Get blockchain analytics and statistics""" - try: - if analytics_type == "blocks": - # Get recent blocks analytics - http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) - head = http_client.get("/rpc/head") - return { - "type": "blocks", - "current_height": head.get("height", 0), - "latest_block": head.get("hash", ""), - "timestamp": head.get("timestamp", ""), - "tx_count": head.get("tx_count", 0), - "status": "Active" - } - - elif analytics_type == "supply": - # Get total supply info - return { - "type": "supply", - "total_supply": "1000000000", # From genesis - "circulating_supply": "999997980", # After transactions - "genesis_minted": "1000000000", - "status": "Available" - } - - elif analytics_type == "accounts": - # Account statistics - return { - "type": "accounts", - "total_accounts": 3, # Genesis + treasury + user - "active_accounts": 2, # Accounts with transactions - "genesis_accounts": 2, # Genesis and treasury - "user_accounts": 1, - "status": "Healthy" - } - - else: - return {"type": analytics_type, "status": "Not implemented yet"} - - except Exception as e: - print(f"Error getting analytics: {e}") - return None - - -def marketplace_operations(action: str, **kwargs) -> Optional[Dict]: - """Handle marketplace operations""" - try: - if action == "list": - return { - "action": "list", - "items": [ - {"name": "AI Compute Hour", "price": 100, "provider": "GPU-Miner-1"}, - {"name": "Storage Space", "price": 50, "provider": "Storage-Node-1"}, - {"name": "Bandwidth", "price": 25, "provider": "Network-Node-1"} - ], - "total_items": 3 - } - - elif action == "create": - return { - "action": "create", - "status": "Item created successfully", - "item_id": "market_" + str(int(time.time())), - "name": kwargs.get("name", ""), - "price": kwargs.get("price", 0) - } - elif action == "buy": - return { - "action": "buy", - "status": "Purchase successful", - "item_id": kwargs.get("item", ""), - "wallet": kwargs.get("wallet", ""), - "price": kwargs.get("price", 0), - "tx_hash": "tx_" + str(int(time.time())) - } - elif action == "orders": - return { - "action": "orders", - "status": "success", - "orders": [], - "count": 0 - } - - else: - return {"action": action, "status": "Not implemented yet"} - - except Exception as e: - print(f"Error in marketplace operations: {e}") - return None - - -def ai_operations(action: str, **kwargs) -> Optional[Dict]: - """Handle AI compute operations""" - try: - if action == "submit": - return { - "action": "submit", - "status": "Job submitted successfully", - "job_id": "ai_job_" + str(int(time.time())), - "model": kwargs.get("model", "default"), - "estimated_time": "30 seconds" - } - - elif action == "status": - return { - "action": "status", - "job_id": kwargs.get("job_id", ""), - "status": "Processing", - "progress": "75%", - "estimated_remaining": "8 seconds" - } - - elif action == "results": - return { - "action": "results", - "job_id": kwargs.get("job_id", ""), - "status": "Completed", - "result": "AI computation completed successfully", - "output": "Sample AI output based on prompt" - } - - elif action == "service_list": - return { - "action": "service_list", - "services": [{"name": "coordinator", "status": "running"}] - } - - elif action == "service_status": - return { - "action": "service_status", - "name": kwargs.get("name", "all"), - "status": "running", - "uptime": "5d 12h" - } - - elif action == "service_test": - return { - "action": "service_test", - "name": kwargs.get("name", "coordinator"), - "status": "passed", - "latency": "120ms" - } - - else: - return {"action": action, "status": "Not implemented yet"} - - except Exception as e: - print(f"Error in AI operations: {e}") - return None - - -def mining_operations(action: str, **kwargs) -> Optional[Dict]: - """Handle mining operations""" - try: - rpc_url = kwargs.get('rpc_url', DEFAULT_RPC_URL) - - if action == "status": - # Query actual blockchain status from RPC - try: - http_client = AITBCHTTPClient(base_url=rpc_url, timeout=5) - head_data = http_client.get("/rpc/head") - actual_height = head_data.get('height', 0) - except Exception: - actual_height = 0 - - return { - "action": "status", - "mining_active": True, - "current_height": actual_height, - "blocks_mined": actual_height, - "rewards_earned": f"{actual_height * 10} AIT", - "hash_rate": "High" - } - - elif action == "rewards": - # Query actual blockchain height for reward calculation - try: - http_client = AITBCHTTPClient(base_url=rpc_url, timeout=5) - head_data = http_client.get("/rpc/head") - actual_height = head_data.get('height', 0) - except Exception: - actual_height = 0 - - total_rewards = actual_height * 10 - return { - "action": "rewards", - "total_rewards": f"{total_rewards} AIT", - "last_reward": "10 AIT" if actual_height > 0 else "0 AIT", - "reward_rate": "10 AIT per block", - "next_reward": "In ~8 seconds" - } - - else: - return {"action": action, "status": "Not implemented yet"} - - except Exception as e: - print(f"Error in mining operations: {e}") - return None - - -def agent_operations(action: str, **kwargs) -> Optional[Dict]: - """Handle AI agent workflow and execution management""" - try: - if action == "create": - return { - "action": "create", - "agent_id": f"agent_{int(time.time())}", - "name": kwargs.get("name", ""), - "status": "Created", - "verification_level": kwargs.get("verification", "basic"), - "max_execution_time": kwargs.get("max_execution_time", 3600), - "max_cost_budget": kwargs.get("max_cost_budget", 0.0) - } - - elif action == "execute": - return { - "action": "execute", - "execution_id": f"exec_{int(time.time())}", - "agent_name": kwargs.get("name", ""), - "status": "Running", - "priority": kwargs.get("priority", "medium"), - "start_time": time.strftime("%Y-%m-%d %H:%M:%S"), - "estimated_completion": "In 5-10 minutes" - } - - elif action == "status": - return { - "action": "status", - "agent_name": kwargs.get("name", "All agents"), - "active_agents": 3, - "completed_executions": 47, - "failed_executions": 2, - "average_execution_time": "3.2 minutes", - "total_cost": "1250 AIT" - } - - elif action == "list": - # Use real coordinator API instead of stub data - try: - import requests - coordinator_url = "http://localhost:9001" - - # Build query parameters - query = {} - if kwargs.get("status"): - query["status"] = kwargs.get("status") - - # Call coordinator API - response = requests.post(f"{coordinator_url}/agents/discover", json=query, timeout=10) - - if response.status_code == 200: - data = response.json() - # Transform coordinator data to CLI format - agents = [] - for agent in data.get("agents", []): - agents.append({ - "name": agent["agent_id"], - "status": agent["status"], - "type": agent["agent_type"], - "capabilities": ", ".join(agent.get("capabilities", [])), - "services": ", ".join(agent.get("services", [])) - }) - - return { - "action": "list", - "agents": agents, - "count": len(agents) - } - else: - # Fallback to stub data if API fails - status_filter = kwargs.get("status") - agents = [ - {"name": "data-analyzer", "status": "active", "executions": 15, "success_rate": "93%"}, - {"name": "trading-bot", "status": "completed", "executions": 23, "success_rate": "87%"}, - {"name": "content-generator", "status": "failed", "executions": 8, "success_rate": "75%"} - ] - - if status_filter: - agents = [a for a in agents if a["status"] == status_filter] - - return { - "action": "list", - "agents": agents, - "count": len(agents) - } - except Exception as e: - # Fallback to stub data on error - status_filter = kwargs.get("status") - agents = [ - {"name": "data-analyzer", "status": "active", "executions": 15, "success_rate": "93%"}, - {"name": "trading-bot", "status": "completed", "executions": 23, "success_rate": "87%"}, - {"name": "content-generator", "status": "failed", "executions": 8, "success_rate": "75%"} - ] - - if status_filter: - agents = [a for a in agents if a["status"] == status_filter] - - return { - "action": "list", - "agents": agents, - "total_count": len(agents) - } - - elif action == "message": - # Send message via blockchain transaction payload - agent = kwargs.get("agent") - message = kwargs.get("message") - wallet = kwargs.get("wallet") - password = kwargs.get("password") - password_file = kwargs.get("password_file") - rpc_url = kwargs.get("rpc_url", DEFAULT_RPC_URL) - - if not agent or not message: - print("Error: agent and message are required") - return None - - if not wallet: - print("Error: wallet is required to send messages") - return None - - # Get password - if password_file: - with open(password_file) as f: - password = f.read().strip() - elif not password: - print("Error: password or password_file is required") - return None - - try: - # Decrypt wallet - keystore_path = DEFAULT_KEYSTORE_DIR / f"{wallet}.json" - private_key_hex = decrypt_private_key(keystore_path, password) - private_key_bytes = bytes.fromhex(private_key_hex) - - # Get sender address - with open(keystore_path) as f: - keystore_data = json.load(f) - sender_address = keystore_data['address'] - - # Create transaction with message as payload - priv_key = ed25519.Ed25519PrivateKey.from_private_bytes(private_key_bytes) - pub_hex = priv_key.public_key().public_bytes( - encoding=serialization.Encoding.Raw, - format=serialization.PublicFormat.Raw - ).hex() - - # Get chain_id from RPC health endpoint or use provided chain_id - chain_id_from_rpc = kwargs.get('chain_id', 'ait-mainnet') - # Auto-detect if not provided - if not kwargs.get('chain_id'): - from aitbc_cli.utils.chain_id import get_chain_id - chain_id_from_rpc = get_chain_id(rpc_url) - try: - http_client = AITBCHTTPClient(base_url=rpc_url, timeout=5) - health_data = http_client.get("/health") - supported_chains = health_data.get("supported_chains", []) - if supported_chains: - chain_id_from_rpc = supported_chains[0] - chain_id = supported_chains[0] - except Exception: - pass - - # Get actual nonce from blockchain - try: - http_client = AITBCHTTPClient(base_url=rpc_url, timeout=5) - account_data = http_client.get(f"/rpc/account/{sender_address}") - actual_nonce = account_data.get("nonce", 0) - except Exception: - actual_nonce = 0 - - tx = { - "type": "TRANSFER", - "chain_id": chain_id, - "from": sender_address, - "nonce": actual_nonce, - "fee": 10, - "payload": { - "recipient": agent, - "amount": 0, - "message": message - } - } - - # Sign transaction - tx_string = json.dumps(tx, sort_keys=True) - tx_hash = hashlib.sha256(tx_string.encode()).hexdigest() - tx["signature"] = priv_key.sign(tx_string.encode()).hex() - tx["public_key"] = pub_hex - - # Submit transaction - try: - http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) - result = http_client.post("/rpc/transaction", json=tx) - print(f"Message sent successfully") - print(f"From: {sender_address}") - print(f"To: {agent}") - print(f"Content: {message}") - return result - except NetworkError as e: - print(f"Error sending message: {e}") - return None - except Exception as e: - print(f"Error sending message: {e}") - return None - except Exception as e: - print(f"Error: {e}") - return None - - elif action == "messages": - # Retrieve messages for an agent - agent = kwargs.get("agent") - wallet = kwargs.get("wallet") - rpc_url = kwargs.get("rpc_url", DEFAULT_RPC_URL) - - if not agent: - print("Error: agent address is required") - return None - - try: - # Since /rpc/transactions endpoint is not implemented, query local database - import sys - sys.path.insert(0, "/opt/aitbc/apps/blockchain-node/src") - from sqlmodel import create_engine, Session, select - from aitbc_chain.models import Transaction - - chain_db_path = get_blockchain_data_path("ait-mainnet") / "chain.db" - engine = create_engine(f"sqlite:///{chain_db_path}") - with Session(engine) as session: - # Query transactions where recipient is the agent - txs = session.exec( - select(Transaction).where(Transaction.recipient == agent) - .order_by(Transaction.timestamp.desc()) - .limit(50) - ).all() - - messages = [] - for tx in txs: - # Extract payload - payload = "" - if hasattr(tx, "tx_metadata") and tx.tx_metadata: - if isinstance(tx.tx_metadata, dict): - payload = tx.tx_metadata.get("payload", "") - elif isinstance(tx.tx_metadata, str): - try: - payload = json.loads(tx.tx_metadata).get("payload", "") - except (json.JSONDecodeError, TypeError): - pass - elif hasattr(tx, "payload") and tx.payload: - if isinstance(tx.payload, dict): - payload = tx.payload.get("payload", "") - - if payload: # Only include transactions with payloads - messages.append({ - "from": tx.sender, - "message": payload, - "timestamp": tx.timestamp, - "block_height": tx.block_height, - "tx_hash": tx.tx_hash - }) - - print(f"Found {len(messages)} messages for {agent}") - for msg in messages: - print(f"From: {msg['from']}") - print(f"Message: {msg['message']}") - print(f"Block: {msg['block_height']}") - print(f"Time: {msg['timestamp']}") - print("-" * 40) - - return { - "action": "messages", - "agent": agent, - "count": len(messages), - "messages": messages - } - - except Exception as e: - print(f"Error retrieving messages: {e}") - return None - - else: - return {"action": action, "status": "Not implemented yet"} - - except Exception as e: - print(f"Error in agent operations: {e}") - return None - - -def hermes_training_operations(action: str, **kwargs) -> Optional[Dict]: - """Handle hermes agent ecosystem operations""" - try: - if action == "deploy": - return { - "action": "deploy", - "deployment_id": f"deploy_{int(time.time())}", - "environment": kwargs.get("environment", "dev"), - "status": "Deploying", - "agent_id": f"hermes_{int(time.time())}", - "estimated_deployment_time": "2-3 minutes", - "deployment_cost": "50 AIT" - } - - elif action == "monitor": - return { - "action": "monitor", - "agent_id": kwargs.get("agent_id", "all"), - "metrics_type": kwargs.get("metrics", "all"), - "performance_score": "94.2%", - "cost_efficiency": "87.5%", - "error_rate": "1.2%", - "uptime": "99.8%", - "last_updated": time.strftime("%Y-%m-%d %H:%M:%S") - } - - elif action == "market": - market_action = kwargs.get("market_action") - if market_action == "list": - return { - "action": "market", - "market_action": "list", - "agents": [ - {"id": "hermes_001", "name": "Data Analysis Pro", "price": 100, "rating": 4.8}, - {"id": "hermes_002", "name": "Trading Expert", "price": 250, "rating": 4.6}, - {"id": "hermes_003", "name": "Content Creator", "price": 75, "rating": 4.9} - ], - "total_available": 3 - } - elif market_action == "publish": - return { - "action": "market", - "market_action": "publish", - "agent_id": kwargs.get("agent_id", ""), - "listing_price": kwargs.get("price", 0), - "status": "Published", - "market_fee": "5 AIT" - } - - elif action == "train": - train_action = kwargs.get("train_action") - if train_action == "agent": - # Load training data - training_data_path = kwargs.get("training_data") - if not training_data_path or not os.path.exists(training_data_path): - return { - "action": "train", - "train_action": "agent", - "status": "error", - "error": "Training data file not found" - } - - try: - with open(training_data_path, 'r') as f: - training_config = json.load(f) - - # Validate training data matches stage - stage = kwargs.get("stage") - if training_config.get('stage') != stage: - return { - "action": "train", - "train_action": "agent", - "status": "error", - "error": f"Training data stage mismatch: expected {stage}, got {training_config.get('stage')}" - } - - # Initialize logging - log_dir = "/var/log/aitbc/agent-training" - os.makedirs(log_dir, exist_ok=True) - log_file = f"{log_dir}/agent_{kwargs.get('agent_id')}_{stage}_{int(time.time())}.log" - - # Execute training operations with actual hermes calls - operations = training_config.get('training_data', {}).get('operations', []) - completed_ops = 0 - failed_ops = 0 - - # hermes service endpoints - agent_coordinator_url = "http://localhost:9001" - exchange_url = "http://localhost:8001" - blockchain_rpc_url = "http://localhost:8006" - - # Write training log with actual hermes calls - for i, op in enumerate(operations, 1): - operation = op.get('operation') - parameters = op.get('parameters', {}) - - log_entry = { - "timestamp": datetime.datetime.now().isoformat(), - "agent_id": kwargs.get('agent_id'), - "stage": stage, - "operation": operation, - "prompt": { - "parameters": parameters, - "expected_result": op.get('expected_result') - } - } - - # Execute training via hermes agent with allowlist enabled - start_time = time.time() - try: - # Build prompt for hermes agent to execute AITBC command - prompt_message = f"Execute AITBC CLI command: {operation}" - if parameters: - prompt_message += f" with parameters: {json.dumps(parameters)}" - - # Use hermes agent with allowlist (AITBC CLI now allowed) - cmd = ["hermes", "agent", "--message", prompt_message, "--agent", "main"] - - try: - result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) - - duration_ms = int((time.time() - start_time) * 1000) - - if result.returncode == 0: - reply = { - "status": "completed", - "result": result.stdout.strip() if result.stdout else "Command executed successfully", - "cli_output": result.stdout.strip() - } - log_entry["status"] = "completed" - completed_ops += 1 - else: - reply = { - "status": "error", - "error": result.stderr.strip() if result.stderr else "Command failed", - "cli_output": result.stdout.strip(), - "cli_error": result.stderr.strip() - } - log_entry["status"] = "failed" - failed_ops += 1 - - log_entry["reply"] = reply - log_entry["duration_ms"] = duration_ms - except subprocess.TimeoutExpired: - duration_ms = int((time.time() - start_time) * 1000) - reply = { - "status": "error", - "error": "Command timed out after 30 seconds" - } - log_entry["reply"] = reply - log_entry["status"] = "failed" - log_entry["duration_ms"] = duration_ms - failed_ops += 1 - except Exception as e: - duration_ms = int((time.time() - start_time) * 1000) - reply = { - "status": "error", - "error": f"Command execution failed: {str(e)}" - } - log_entry["reply"] = reply - log_entry["status"] = "failed" - log_entry["duration_ms"] = duration_ms - failed_ops += 1 - - except Exception as e: - duration_ms = int((time.time() - start_time) * 1000) - log_entry["reply"] = { - "status": "error", - "error": str(e) - } - log_entry["status"] = "failed" - log_entry["duration_ms"] = duration_ms - failed_ops += 1 - - with open(log_file, 'a') as f: - f.write(json.dumps(log_entry) + '\n') - - return { - "action": "train", - "train_action": "agent", - "agent_id": kwargs.get("agent_id"), - "stage": stage, - "total_operations": len(operations), - "completed": completed_ops, - "failed": failed_ops, - "success_rate": f"{(completed_ops/len(operations)*100):.1f}%" if operations else "0%", - "log_file": log_file, - "status": "success" - } - except Exception as e: - return { - "action": "train", - "train_action": "agent", - "status": "error", - "error": str(e) - } - - elif train_action == "validate": - # Load training data for validation - training_data_path = f"/opt/aitbc/docs/agent-training/{kwargs.get('stage')}.json" - try: - with open(training_data_path, 'r') as f: - training_config = json.load(f) - except Exception as e: - return { - "action": "train", - "train_action": "validate", - "status": "error", - "error": f"Failed to load training data: {e}" - } - - # Run exam tests (simulated) - exam_tests = training_config.get('validation', {}).get('exam_tests', []) - passed_tests = len(exam_tests) - score = 100 if exam_tests else 0 - - return { - "action": "train", - "train_action": "validate", - "agent_id": kwargs.get("agent_id"), - "stage": kwargs.get("stage"), - "total_tests": len(exam_tests), - "passed_tests": passed_tests, - "score": f"{score}%", - "validation": "passed" if score >= 80 else "failed", - "status": "success" - } - - elif train_action == "certify": - # Check all stages (simulated) - stages = [ - "stage1_foundation", - "stage2_operations_mastery", - "stage3_ai_operations", - "stage4_marketplace_economics", - "stage5_expert_operations", - "stage6_agent_identity_sdk", - "stage7_cross_node_training", - "stage8_advanced_agent_specialization", - "stage9_multi_chain_architecture" - ] - - return { - "action": "train", - "train_action": "certify", - "agent_id": kwargs.get("agent_id"), - "certification_status": "fully_certified", - "certified_stages": stages, - "total_stages": len(stages), - "certified_count": len(stages), - "status": "success" - } - else: - return {"action": "market", "market_action": market_action, "status": "Not implemented yet"} - - else: - return {"action": action, "status": "Not implemented yet"} - - except Exception as e: - print(f"Error in hermes operations: {e}") - return None - - -def workflow_operations(action: str, **kwargs) -> Optional[Dict]: - """Handle workflow automation and management""" - try: - if action == "create": - return { - "action": "create", - "workflow_id": f"workflow_{int(time.time())}", - "name": kwargs.get("name", ""), - "template": kwargs.get("template", "custom"), - "status": "Created", - "steps": 5, - "estimated_duration": "10-15 minutes" - } - - elif action == "run": - return { - "action": "run", - "workflow_name": kwargs.get("name", ""), - "execution_id": f"wf_exec_{int(time.time())}", - "status": "Running", - "async_execution": kwargs.get("async_exec", False), - "progress": "0%", - "start_time": time.strftime("%Y-%m-%d %H:%M:%S") - } - - else: - return {"action": action, "status": "Not implemented yet"} - - except Exception as e: - print(f"Error in workflow operations: {e}") - return None - - -def resource_operations(action: str, **kwargs) -> Optional[Dict]: - """Handle resource management and optimization""" - try: - if action == "status": - resource_type = kwargs.get("type", "all") - return { - "action": "status", - "resource_type": resource_type, - "cpu_utilization": "45.2%", - "memory_usage": "2.1GB / 8GB (26%)", - "storage_available": "45GB / 100GB", - "network_bandwidth": "120Mbps / 1Gbps", - "active_agents": 3, - "resource_efficiency": "78.5%" - } - - elif action == "allocate": - return { - "action": "allocate", - "agent_id": kwargs.get("agent_id", ""), - "allocated_cpu": kwargs.get("cpu", 0), - "allocated_memory": kwargs.get("memory", 0), - "duration_minutes": kwargs.get("duration", 0), - "cost_per_hour": "25 AIT", - "status": "Allocated", - "allocation_id": f"alloc_{int(time.time())}" - } - - elif action == "optimize": - return { - "action": "optimize", - "target": kwargs.get("target", "all"), - "agent_id": kwargs.get("agent_id", ""), - "optimization_score": "85.2%", - "improvement": "12.5%", - "status": "Optimized" - } - - elif action == "benchmark": - return { - "action": "benchmark", - "type": kwargs.get("type", "all"), - "score": 9850, - "percentile": "92nd", - "status": "Completed" - } - - elif action == "monitor": - return { - "action": "monitor", - "message": "Monitoring started", - "interval": kwargs.get("interval", 5), - "duration": kwargs.get("duration", 60) - } - - else: - return {"action": action, "status": "Not implemented yet"} - - except Exception as e: - print(f"Error in resource operations: {e}") - return None - - -def get_chain_info(rpc_url: str = DEFAULT_RPC_URL) -> Optional[Dict]: - """Get blockchain information""" - try: - result = {} - # Get chain metadata from health endpoint - http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) - health = http_client.get("/health") - chains = health.get('supported_chains', []) - result['chain_id'] = chains[0] if chains else 'ait-mainnet' - result['supported_chains'] = ', '.join(chains) if chains else 'ait-mainnet' - result['proposer_id'] = health.get('proposer_id', '') - # Get head block for height - head = http_client.get("/rpc/head") - result['height'] = head.get('height', 0) - result['hash'] = head.get('hash', "") - result['timestamp'] = head.get('timestamp', 'N/A') - result['tx_count'] = head.get('tx_count', 0) - return result if result else None - except NetworkError as e: - print(f"Error: {e}") - return None - except Exception as e: - print(f"Error: {e}") - return None - - -def get_network_status(rpc_url: str = DEFAULT_RPC_URL) -> Optional[Dict]: - """Get network status and health""" - try: - # Get head block - http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) - return http_client.get("/rpc/head") - except NetworkError as e: - print(f"Error getting network status: {e}") - return None - except Exception as e: - print(f"Error: {e}") - return None - - -def get_blockchain_analytics(analytics_type: str, limit: int = 10, rpc_url: str = DEFAULT_RPC_URL) -> Optional[Dict]: - """Get blockchain analytics and statistics""" - try: - if analytics_type == "blocks": - # Get recent blocks analytics - http_client = AITBCHTTPClient(base_url=rpc_url, timeout=30) - head = http_client.get("/rpc/head") - return { - "type": "blocks", - "current_height": head.get("height", 0), - "latest_block": head.get("hash", ""), - "timestamp": head.get("timestamp", ""), - "tx_count": head.get("tx_count", 0), - "status": "Active" - } - - elif analytics_type == "supply": - # Get total supply info - return { - "type": "supply", - "total_supply": "1000000000", # From genesis - "circulating_supply": "999997980", # After transactions - "genesis_minted": "1000000000", - "status": "Available" - } - - elif analytics_type == "accounts": - # Account statistics - return { - "type": "accounts", - "total_accounts": 3, # Genesis + treasury + user - "active_accounts": 2, # Accounts with transactions - "genesis_accounts": 2, # Genesis and treasury - "user_accounts": 1, - "status": "Healthy" - } - - else: - return {"type": analytics_type, "status": "Not implemented yet"} - - except Exception as e: - print(f"Error getting analytics: {e}") - return None - - -def simulate_blockchain(blocks: int, transactions: int, delay: float) -> Dict: - """Simulate blockchain block production and transactions""" - print(f"Simulating blockchain with {blocks} blocks, {transactions} transactions per block") - - results = [] - for block_num in range(blocks): - # Simulate block production - block_data = { - 'block_number': block_num + 1, - 'timestamp': time.time(), - 'transactions': [] - } - - # Generate transactions - for tx_num in range(transactions): - tx = { - 'tx_id': f"0x{random.getrandbits(256):064x}", - 'from_address': f"ait{random.getrandbits(160):040x}", - 'to_address': f"ait{random.getrandbits(160):040x}", - 'amount': random.uniform(0.1, 1000.0), - 'fee': random.uniform(0.01, 1.0) - } - block_data['transactions'].append(tx) - - block_data['tx_count'] = len(block_data['transactions']) - block_data['total_amount'] = sum(tx['amount'] for tx in block_data['transactions']) - block_data['total_fees'] = sum(tx['fee'] for tx in block_data['transactions']) - - results.append(block_data) - - # Output block info - print(f"Block {block_data['block_number']}: {block_data['tx_count']} txs, " - f"{block_data['total_amount']:.2f} AIT, {block_data['total_fees']:.2f} fees") - - if delay > 0 and block_num < blocks - 1: - time.sleep(delay) - - # Summary - total_txs = sum(block['tx_count'] for block in results) - total_amount = sum(block['total_amount'] for block in results) - total_fees = sum(block['total_fees'] for block in results) - - print(f"\nSimulation Summary:") - print(f" Total Blocks: {blocks}") - print(f" Total Transactions: {total_txs}") - print(f" Total Amount: {total_amount:.2f} AIT") - print(f" Total Fees: {total_fees:.2f} AIT") - print(f" Average TPS: {total_txs / (blocks * max(delay, 0.1)):.2f}") - - return { - 'action': 'simulate_blockchain', - 'blocks': blocks, - 'total_transactions': total_txs, - 'total_amount': total_amount, - 'total_fees': total_fees - } - - -def simulate_wallets(wallets: int, balance: float, transactions: int, amount_range: str) -> Dict: - """Simulate wallet creation and transactions""" - print(f"Simulating {wallets} wallets with {balance:.2f} AIT initial balance") - - # Parse amount range - try: - min_amount, max_amount = map(float, amount_range.split('-')) - except ValueError: - min_amount, max_amount = 1.0, 100.0 - - # Create wallets - created_wallets = [] - for i in range(wallets): - wallet = { - 'name': f'sim_wallet_{i+1}', - 'address': f"ait{random.getrandbits(160):040x}", - 'balance': balance - } - created_wallets.append(wallet) - print(f"Created wallet {wallet['name']}: {wallet['address']} with {balance:.2f} AIT") - - # Simulate transactions - print(f"\nSimulating {transactions} transactions...") - for i in range(transactions): - # Random sender and receiver - sender = random.choice(created_wallets) - receiver = random.choice([w for w in created_wallets if w != sender]) - - # Random amount - amount = random.uniform(min_amount, max_amount) - - # Check if sender has enough balance - if sender['balance'] >= amount: - sender['balance'] -= amount - receiver['balance'] += amount - - print(f"Tx {i+1}: {sender['name']} -> {receiver['name']}: {amount:.2f} AIT") - else: - print(f"Tx {i+1}: {sender['name']} -> {receiver['name']}: FAILED (insufficient balance)") - - # Final balances - print(f"\nFinal Wallet Balances:") - for wallet in created_wallets: - print(f" {wallet['name']}: {wallet['balance']:.2f} AIT") - - return { - 'action': 'simulate_wallets', - 'wallets': wallets, - 'initial_balance': balance, - 'transactions': transactions - } - - -def simulate_price(price: float, volatility: float, timesteps: int, delay: float) -> Dict: - """Simulate AIT price movements""" - print(f"Simulating AIT price from {price:.2f} with {volatility:.2f} volatility") - - current_price = price - prices = [current_price] - - for step in range(timesteps): - # Random price change - change_percent = random.uniform(-volatility, volatility) - current_price = current_price * (1 + change_percent) - - # Ensure price doesn't go negative - current_price = max(current_price, 0.01) - - prices.append(current_price) - - print(f"Step {step+1}: {current_price:.4f} AIT ({change_percent:+.2%})") - - if delay > 0 and step < timesteps - 1: - time.sleep(delay) - - # Statistics - min_price = min(prices) - max_price = max(prices) - avg_price = sum(prices) / len(prices) - - print(f"\nPrice Statistics:") - print(f" Starting Price: {price:.4f} AIT") - print(f" Ending Price: {current_price:.4f} AIT") - print(f" Minimum Price: {min_price:.4f} AIT") - print(f" Maximum Price: {max_price:.4f} AIT") - print(f" Average Price: {avg_price:.4f} AIT") - print(f" Total Change: {((current_price - price) / price * 100):+.2f}%") - - return { - 'action': 'simulate_price', - 'starting_price': price, - 'ending_price': current_price, - 'min_price': min_price, - 'max_price': max_price, - 'avg_price': avg_price - } - - -def simulate_network(nodes: int, network_delay: float, failure_rate: float) -> Dict: - """Simulate network topology and node failures""" - print(f"Simulating network with {nodes} nodes, {network_delay}s delay, {failure_rate:.2f} failure rate") - - # Create nodes - network_nodes = [] - for i in range(nodes): - node = { - 'id': f'node_{i+1}', - 'address': f"10.1.223.{90+i}", - 'status': 'active', - 'height': 0, - 'connected_to': [] - } - network_nodes.append(node) - - # Create network topology (ring + mesh) - for i, node in enumerate(network_nodes): - # Connect to next node (ring) - next_node = network_nodes[(i + 1) % len(network_nodes)] - node['connected_to'].append(next_node['id']) - - # Connect to random nodes (mesh) - if len(network_nodes) > 2: - mesh_connections = random.sample([n['id'] for n in network_nodes if n['id'] != node['id']], - min(2, len(network_nodes) - 1)) - for conn in mesh_connections: - if conn not in node['connected_to']: - node['connected_to'].append(conn) - - # Display network topology - print(f"\nNetwork Topology:") - for node in network_nodes: - print(f" {node['id']} ({node['address']}): connected to {', '.join(node['connected_to'])}") - - # Simulate network operations - print(f"\nSimulating network operations...") - active_nodes = network_nodes.copy() - - for step in range(10): - # Simulate failures - for node in active_nodes: - if random.random() < failure_rate: - node['status'] = 'failed' - print(f"Step {step+1}: {node['id']} failed") - - # Remove failed nodes - active_nodes = [n for n in active_nodes if n['status'] == 'active'] - - # Simulate block propagation - if active_nodes: - # Random node produces block - producer = random.choice(active_nodes) - producer['height'] += 1 - - # Propagate to connected nodes - for node in active_nodes: - if node['id'] != producer['id'] and node['id'] in producer['connected_to']: - node['height'] = max(node['height'], producer['height'] - 1) - - print(f"Step {step+1}: {producer['id']} produced block {producer['height']}, " - f"{len(active_nodes)} nodes active") - - time.sleep(network_delay) - - # Final network status - print(f"\nFinal Network Status:") - for node in network_nodes: - status_icon = "✅" if node['status'] == 'active' else "❌" - print(f" {status_icon} {node['id']}: height {node['height']}, " - f"connections: {len(node['connected_to'])}") - - return { - 'action': 'simulate_network', - 'nodes': nodes, - 'active_nodes': len(active_nodes), - 'failure_rate': failure_rate - } - - -def simulate_ai_jobs(jobs: int, models: str, duration_range: str) -> Dict: - """Simulate AI job submission and processing""" - print(f"Simulating {jobs} AI jobs with models: {models}") - - # Parse models - model_list = [m.strip() for m in models.split(',')] - - # Parse duration range - try: - min_duration, max_duration = map(int, duration_range.split('-')) - except ValueError: - min_duration, max_duration = 30, 300 - - # Simulate job submission - submitted_jobs = [] - for i in range(jobs): - job = { - 'job_id': f"job_{i+1:03d}", - 'model': random.choice(model_list), - 'status': 'queued', - 'submit_time': time.time(), - 'duration': random.randint(min_duration, max_duration), - 'wallet': f"wallet_{random.randint(1, 5):03d}" - } - submitted_jobs.append(job) - - print(f"Submitted job {job['job_id']}: {job['model']} (est. {job['duration']}s)") - - # Simulate job processing - print(f"\nSimulating job processing...") - processing_jobs = submitted_jobs.copy() - completed_jobs = [] - - current_time = time.time() - while processing_jobs and current_time < time.time() + 600: # Max 10 minutes - current_time = time.time() - - for job in processing_jobs[:]: - if job['status'] == 'queued' and current_time - job['submit_time'] > 5: - job['status'] = 'running' - job['start_time'] = current_time - print(f"Started {job['job_id']}") - - elif job['status'] == 'running': - if current_time - job['start_time'] >= job['duration']: - job['status'] = 'completed' - job['end_time'] = current_time - job['actual_duration'] = job['end_time'] - job['start_time'] - processing_jobs.remove(job) - completed_jobs.append(job) - print(f"Completed {job['job_id']} in {job['actual_duration']:.1f}s") - - time.sleep(1) # Check every second - - # Job statistics - print(f"\nJob Statistics:") - print(f" Total Jobs: {jobs}") - print(f" Completed Jobs: {len(completed_jobs)}") - print(f" Failed Jobs: {len(processing_jobs)}") - - if completed_jobs: - avg_duration = sum(job['actual_duration'] for job in completed_jobs) / len(completed_jobs) - print(f" Average Duration: {avg_duration:.1f}s") - - # Model statistics - model_stats = {} - for job in completed_jobs: - model_stats[job['model']] = model_stats.get(job['model'], 0) + 1 - - print(f" Model Usage:") - for model, count in model_stats.items(): - print(f" {model}: {count} jobs") - - return { - 'action': 'simulate_ai_jobs', - 'total_jobs': jobs, - 'completed_jobs': len(completed_jobs), - 'failed_jobs': len(processing_jobs) - } - - -def legacy_main(): - parser = argparse.ArgumentParser(description="AITBC CLI - Comprehensive Blockchain Management Tool") - parser.add_argument("--chain-id", default=None, help="Chain ID (auto-detected from blockchain node if not provided)") - subparsers = parser.add_subparsers(dest="command", help="Available commands") - - # Create wallet command - create_parser = subparsers.add_parser("create", help="Create a new wallet") - create_parser.add_argument("--name", required=True, help="Wallet name") - create_parser.add_argument("--password", help="Wallet password") - create_parser.add_argument("--password-file", help="File containing wallet password") - - # Send transaction command - send_parser = subparsers.add_parser("send", help="Send AIT") - send_parser.add_argument("--from", required=True, dest="from_wallet", help="From wallet name") - send_parser.add_argument("--to", required=True, dest="to_address", help="To address") - send_parser.add_argument("--amount", type=float, required=True, help="Amount to send") - send_parser.add_argument("--fee", type=float, default=10.0, help="Transaction fee") - send_parser.add_argument("--password", help="Wallet password") - send_parser.add_argument("--password-file", help="File containing wallet password") - send_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") - - # List wallets command - list_parser = subparsers.add_parser("list", help="List wallets") - list_parser.add_argument("--format", choices=["table", "json"], default="table", help="Output format") - - # Balance command - balance_parser = subparsers.add_parser("balance", help="Get wallet balance") - balance_parser.add_argument("--name", required=True, help="Wallet name") - balance_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") - - # Transactions command - tx_parser = subparsers.add_parser("transactions", help="Get wallet transactions") - tx_parser.add_argument("--name", required=True, help="Wallet name") - tx_parser.add_argument("--limit", type=int, default=10, help="Number of transactions") - tx_parser.add_argument("--format", choices=["table", "json"], default="table", help="Output format") - tx_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") - - # Chain info command - chain_parser = subparsers.add_parser("chain", help="Get blockchain information") - chain_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") - - # Network status command - network_parser = subparsers.add_parser("network", help="Get network status") - network_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") - - # Blockchain analytics command - analytics_parser = subparsers.add_parser("analytics", help="Blockchain analytics and statistics") - analytics_parser.add_argument("--type", choices=["blocks", "transactions", "accounts", "supply"], - default="blocks", help="Analytics type") - analytics_parser.add_argument("--limit", type=int, default=10, help="Number of items to analyze") - analytics_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") - - # Marketplace operations command - market_parser = subparsers.add_parser("marketplace", help="Marketplace operations") - market_parser.add_argument("--action", choices=["list", "create", "search", "my-listings"], - required=True, help="Marketplace action") - market_parser.add_argument("--name", help="Item name") - market_parser.add_argument("--price", type=float, help="Item price") - market_parser.add_argument("--description", help="Item description") - market_parser.add_argument("--wallet", help="Wallet name for marketplace operations") - market_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") - - # AI operations command - ai_parser = subparsers.add_parser("ai-ops", help="AI compute operations") - ai_parser.add_argument("--action", choices=["submit", "status", "results"], - required=True, help="AI operation") - ai_parser.add_argument("--model", help="AI model name") - ai_parser.add_argument("--prompt", help="AI prompt") - ai_parser.add_argument("--job-id", help="Job ID for status/results") - ai_parser.add_argument("--wallet", help="Wallet name for AI operations") - ai_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") - - # Mining operations command - mining_parser = subparsers.add_parser("mining", help="Mining operations and status") - mining_parser.add_argument("--action", choices=["status", "start", "stop", "rewards"], - help="Mining action") - mining_parser.add_argument("--wallet", help="Wallet name for mining rewards") - mining_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") - - # Agent management commands (hermes agent focused) - agent_parser = subparsers.add_parser("agent", help="AI agent workflow and execution management") - agent_subparsers = agent_parser.add_subparsers(dest="agent_action", help="Agent actions") - - # Agent create - agent_create_parser = agent_subparsers.add_parser("create", help="Create new AI agent workflow") - agent_create_parser.add_argument("--name", required=True, help="Agent workflow name") - agent_create_parser.add_argument("--description", help="Agent description") - agent_create_parser.add_argument("--workflow-file", help="Workflow definition from JSON file") - agent_create_parser.add_argument("--verification", choices=["basic", "full", "zero-knowledge"], - default="basic", help="Verification level") - agent_create_parser.add_argument("--max-execution-time", type=int, default=3600, help="Max execution time (seconds)") - agent_create_parser.add_argument("--max-cost-budget", type=float, default=0.0, help="Max cost budget") - - # Agent execute - agent_execute_parser = agent_subparsers.add_parser("execute", help="Execute AI agent workflow") - agent_execute_parser.add_argument("--name", required=True, help="Agent workflow name") - agent_execute_parser.add_argument("--input-data", help="Input data for agent execution") - agent_execute_parser.add_argument("--wallet", help="Wallet for payment") - agent_execute_parser.add_argument("--priority", choices=["low", "medium", "high"], default="medium", help="Execution priority") - - # Agent status - agent_status_parser = agent_subparsers.add_parser("status", help="Check agent execution status") - agent_status_parser.add_argument("--name", help="Agent workflow name") - agent_status_parser.add_argument("--execution-id", help="Specific execution ID") - - # Agent list - agent_list_parser = agent_subparsers.add_parser("list", help="List available agent workflows") - agent_list_parser.add_argument("--status", choices=["active", "completed", "failed"], help="Filter by status") - - # hermes specific commands - hermes_parser = subparsers.add_parser("hermes", help="hermes agent ecosystem operations") - hermes_subparsers = hermes_parser.add_subparsers(dest="hermes_action", help="hermes actions") - - # hermes deploy - hermes_deploy_parser = hermes_subparsers.add_parser("deploy", help="Deploy hermes agent") - hermes_deploy_parser.add_argument("--agent-file", required=True, help="Agent configuration file") - hermes_deploy_parser.add_argument("--wallet", required=True, help="Wallet for deployment costs") - hermes_deploy_parser.add_argument("--environment", choices=["dev", "staging", "prod"], default="dev", help="Deployment environment") - - # hermes monitor - hermes_monitor_parser = hermes_subparsers.add_parser("monitor", help="Monitor hermes agent performance") - hermes_monitor_parser.add_argument("--agent-id", help="Specific agent ID to monitor") - hermes_monitor_parser.add_argument("--metrics", choices=["performance", "cost", "errors", "all"], default="all", help="Metrics to show") - - # hermes market - hermes_market_parser = hermes_subparsers.add_parser("market", help="hermes agent marketplace") - hermes_market_parser.add_argument("--action", choices=["list", "publish", "purchase", "evaluate"], - required=True, help="Market action") - hermes_market_parser.add_argument("--agent-id", help="Agent ID for market operations") - hermes_market_parser.add_argument("--price", type=float, help="Price for market operations") - - # Workflow automation commands - workflow_parser = subparsers.add_parser("workflow", help="Workflow automation and management") - workflow_subparsers = workflow_parser.add_subparsers(dest="workflow_action", help="Workflow actions") - - # Workflow create - workflow_create_parser = workflow_subparsers.add_parser("create", help="Create automated workflow") - workflow_create_parser.add_argument("--name", required=True, help="Workflow name") - workflow_create_parser.add_argument("--template", help="Workflow template") - workflow_create_parser.add_argument("--config-file", help="Workflow configuration file") - - # Workflow run - workflow_run_parser = workflow_subparsers.add_parser("run", help="Execute automated workflow") - workflow_run_parser.add_argument("--name", required=True, help="Workflow name") - workflow_run_parser.add_argument("--params", help="Workflow parameters (JSON)") - workflow_run_parser.add_argument("--async-exec", action="store_true", help="Run asynchronously") - - # Resource management commands - resource_parser = subparsers.add_parser("resource", help="Resource management and optimization") - resource_subparsers = resource_parser.add_subparsers(dest="resource_action", help="Resource actions") - - # Resource status - resource_status_parser = resource_subparsers.add_parser("status", help="Check resource utilization") - resource_status_parser.add_argument("--type", choices=["cpu", "memory", "storage", "network", "all"], default="all") - - # Resource allocate - resource_allocate_parser = resource_subparsers.add_parser("allocate", help="Allocate resources to agent") - resource_allocate_parser.add_argument("--agent-id", required=True, help="Agent ID") - resource_allocate_parser.add_argument("--cpu", type=float, help="CPU cores") - resource_allocate_parser.add_argument("--memory", type=int, help="Memory in MB") - resource_allocate_parser.add_argument("--duration", type=int, help="Duration in minutes") - - # System status command - system_parser = subparsers.add_parser("system", help="System status and information") - system_parser.add_argument("--status", action="store_true", help="Show system status") - - # Genesis command with subcommands - genesis_parser = subparsers.add_parser("genesis", help="Genesis block and wallet generation") - genesis_subparsers = genesis_parser.add_subparsers(dest="genesis_action", help="Genesis actions") - - # Genesis init - genesis_init_parser = genesis_subparsers.add_parser("init", help="Initialize genesis block and wallet") - genesis_init_parser.add_argument("--chain-id", default="ait-mainnet", help="Chain ID for genesis") - genesis_init_parser.add_argument("--create-wallet", action="store_true", help="Create genesis wallet with secure random key") - genesis_init_parser.add_argument("--password", help="Wallet password (auto-generated if not provided)") - genesis_init_parser.add_argument("--proposer", help="Proposer address (defaults to genesis wallet)") - genesis_init_parser.add_argument("--force", action="store_true", help="Force overwrite existing genesis") - genesis_init_parser.add_argument("--register-service", action="store_true", help="Register genesis wallet with wallet service") - genesis_init_parser.add_argument("--service-url", default="http://localhost:8003", help="Wallet service URL") - - # Genesis verify - genesis_verify_parser = genesis_subparsers.add_parser("verify", help="Verify genesis block and wallet configuration") - genesis_verify_parser.add_argument("--chain-id", default="ait-mainnet", help="Chain ID to verify") - - # Genesis info - genesis_info_parser = genesis_subparsers.add_parser("info", help="Show genesis block information") - genesis_info_parser.add_argument("--chain-id", default="ait-mainnet", help="Chain ID to show info for") - - # Blockchain command with subcommands - blockchain_parser = subparsers.add_parser("blockchain", help="Blockchain operations") - blockchain_subparsers = blockchain_parser.add_subparsers(dest="blockchain_action", help="Blockchain actions") - - # Blockchain info - blockchain_info_parser = blockchain_subparsers.add_parser("info", help="Blockchain information") - blockchain_info_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") - - # Blockchain height - blockchain_height_parser = blockchain_subparsers.add_parser("height", help="Blockchain height") - blockchain_height_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") - - # Block info - blockchain_block_parser = blockchain_subparsers.add_parser("block", help="Block information") - blockchain_block_parser.add_argument("--number", type=int, help="Block number") - blockchain_block_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") - - # Wallet command with subcommands - wallet_parser = subparsers.add_parser("wallet", help="Wallet operations") - wallet_subparsers = wallet_parser.add_subparsers(dest="wallet_action", help="Wallet actions") - - # Wallet backup - wallet_backup_parser = wallet_subparsers.add_parser("backup", help="Backup wallet") - wallet_backup_parser.add_argument("--name", required=True, help="Wallet name") - wallet_backup_parser.add_argument("--password", help="Wallet password") - wallet_backup_parser.add_argument("--password-file", help="File containing wallet password") - - # Wallet export - wallet_export_parser = wallet_subparsers.add_parser("export", help="Export wallet") - wallet_export_parser.add_argument("--name", required=True, help="Wallet name") - wallet_export_parser.add_argument("--password", help="Wallet password") - wallet_export_parser.add_argument("--password-file", help="File containing wallet password") - - # Wallet sync - wallet_sync_parser = wallet_subparsers.add_parser("sync", help="Sync wallet") - wallet_sync_parser.add_argument("--name", help="Wallet name") - wallet_sync_parser.add_argument("--all", action="store_true", help="Sync all wallets") - - # Wallet balance - wallet_balance_parser = wallet_subparsers.add_parser("balance", help="Wallet balance") - wallet_balance_parser.add_argument("--name", help="Wallet name") - wallet_balance_parser.add_argument("--all", action="store_true", help="Show all balances") - wallet_balance_parser.add_argument("--chain-id", help="Chain ID for multichain operations (e.g., ait-mainnet, ait-devnet)") - - # All balances command (keep for backward compatibility) - all_balances_parser = subparsers.add_parser("all-balances", help="Show all wallet balances") - - # Import wallet command - import_parser = subparsers.add_parser("import", help="Import wallet from private key") - import_parser.add_argument("--name", required=True, help="Wallet name") - import_parser.add_argument("--private-key", required=True, help="Private key (hex)") - import_parser.add_argument("--password", help="Wallet password") - import_parser.add_argument("--password-file", help="File containing wallet password") - - # Export wallet command - export_parser = subparsers.add_parser("export", help="Export private key from wallet") - export_parser.add_argument("--name", required=True, help="Wallet name") - export_parser.add_argument("--password", help="Wallet password") - export_parser.add_argument("--password-file", help="File containing wallet password") - - # Delete wallet command - delete_parser = subparsers.add_parser("delete", help="Delete wallet") - delete_parser.add_argument("--name", required=True, help="Wallet name") - delete_parser.add_argument("--confirm", action="store_true", help="Confirm deletion") - - # Rename wallet command - rename_parser = subparsers.add_parser("rename", help="Rename wallet") - rename_parser.add_argument("--old", required=True, dest="old_name", help="Current wallet name") - rename_parser.add_argument("--new", required=True, dest="new_name", help="New wallet name") - - # Batch send command - batch_parser = subparsers.add_parser("batch", help="Send multiple transactions") - batch_parser.add_argument("--file", required=True, help="JSON file with transactions") - batch_parser.add_argument("--password", help="Wallet password") - batch_parser.add_argument("--password-file", help="File containing wallet password") - batch_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") - - # Update existing mining parser to add flag support - mining_parser.add_argument("--start", action="store_true", help="Start mining") - mining_parser.add_argument("--stop", action="store_true", help="Stop mining") - mining_parser.add_argument("--status", action="store_true", help="Mining status") - - # Update existing network parser to add subcommands - network_subparsers = network_parser.add_subparsers(dest="network_action", help="Network actions") - - # Network status - network_status_parser = network_subparsers.add_parser("status", help="Network status") - network_status_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") - - # Network peers - network_peers_parser = network_subparsers.add_parser("peers", help="Network peers") - network_peers_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") - - # Network sync - network_sync_parser = network_subparsers.add_parser("sync", help="Network sync") - network_sync_parser.add_argument("--status", action="store_true", help="Sync status") - network_sync_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") - - # Network ping - network_ping_parser = network_subparsers.add_parser("ping", help="Ping node") - network_ping_parser.add_argument("--node", help="Node to ping") - network_ping_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") - - # Network propagate - network_propagate_parser = network_subparsers.add_parser("propagate", help="Propagate data") - network_propagate_parser.add_argument("--data", help="Data to propagate") - network_propagate_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") - - # Marketplace commands - market_list_parser = subparsers.add_parser("market-list", help="List marketplace items") - market_list_parser.add_argument("--rpc-url", default=DEFAULT_RPC_URL, help="RPC URL") - - market_create_parser = subparsers.add_parser("market-create", help="Create marketplace listing") - market_create_parser.add_argument("--wallet", required=True, help="Seller wallet name") - market_create_parser.add_argument("--type", required=True, help="Item type") - market_create_parser.add_argument("--price", type=float, required=True, help="Price in AIT") - market_create_parser.add_argument("--description", required=True, help="Item description") - market_create_parser.add_argument("--password", help="Wallet password") - market_create_parser.add_argument("--password-file", help="File containing wallet password") - - # AI commands - ai_submit_parser = subparsers.add_parser("ai-submit", help="Submit AI compute job") - ai_submit_parser.add_argument("--wallet", required=True, help="Client wallet name") - ai_submit_parser.add_argument("--type", required=True, help="Job type") - ai_submit_parser.add_argument("--prompt", required=True, help="AI prompt") - ai_submit_parser.add_argument("--payment", type=float, required=True, help="Payment in AIT") - ai_submit_parser.add_argument("--password", help="Wallet password") - ai_submit_parser.add_argument("--password-file", help="File containing wallet password") - - # Simulation commands - simulate_parser = subparsers.add_parser("simulate", help="Simulate blockchain scenarios and test environments") - simulate_subparsers = simulate_parser.add_subparsers(dest="simulate_command", help="Simulation commands") - - # Blockchain simulation - blockchain_sim_parser = simulate_subparsers.add_parser("blockchain", help="Simulate blockchain block production and transactions") - blockchain_sim_parser.add_argument("--blocks", type=int, default=10, help="Number of blocks to simulate") - blockchain_sim_parser.add_argument("--transactions", type=int, default=50, help="Number of transactions per block") - blockchain_sim_parser.add_argument("--delay", type=float, default=1.0, help="Delay between blocks (seconds)") - - # Wallet simulation - wallets_sim_parser = simulate_subparsers.add_parser("wallets", help="Simulate wallet creation and transactions") - wallets_sim_parser.add_argument("--wallets", type=int, default=5, help="Number of wallets to create") - wallets_sim_parser.add_argument("--balance", type=float, default=1000.0, help="Initial balance for each wallet") - wallets_sim_parser.add_argument("--transactions", type=int, default=20, help="Number of transactions to simulate") - wallets_sim_parser.add_argument("--amount-range", default="1.0-100.0", help="Transaction amount range (min-max)") - - # Price simulation - price_sim_parser = simulate_subparsers.add_parser("price", help="Simulate AIT price movements") - price_sim_parser.add_argument("--price", type=float, default=100.0, help="Starting AIT price") - price_sim_parser.add_argument("--volatility", type=float, default=0.05, help="Price volatility (0.0-1.0)") - price_sim_parser.add_argument("--timesteps", type=int, default=100, help="Number of timesteps to simulate") - price_sim_parser.add_argument("--delay", type=float, default=0.1, help="Delay between timesteps (seconds)") - - # Network simulation - network_sim_parser = simulate_subparsers.add_parser("network", help="Simulate network topology and node failures") - network_sim_parser.add_argument("--nodes", type=int, default=3, help="Number of nodes to simulate") - network_sim_parser.add_argument("--network-delay", type=float, default=0.1, help="Network delay in seconds") - network_sim_parser.add_argument("--failure-rate", type=float, default=0.05, help="Node failure rate (0.0-1.0)") - - # AI jobs simulation - ai_jobs_sim_parser = simulate_subparsers.add_parser("ai-jobs", help="Simulate AI job submission and processing") - ai_jobs_sim_parser.add_argument("--jobs", type=int, default=10, help="Number of AI jobs to simulate") - ai_jobs_sim_parser.add_argument("--models", default="text-generation", help="Available models (comma-separated)") - ai_jobs_sim_parser.add_argument("--duration-range", default="30-300", help="Job duration range in seconds (min-max)") - - args = parser.parse_args() - - # Handle chain_id with auto-detection - from aitbc_cli.utils.chain_id import get_chain_id - chain_id = get_chain_id(DEFAULT_RPC_URL, override=args.chain_id) - - if args.command == "create": - # Get password - password = None - if args.password: - password = args.password - elif args.password_file: - with open(args.password_file) as f: - password = f.read().strip() - else: - import getpass - password = getpass.getpass("Enter wallet password: ") - - if not password: - print("Error: Password is required") - sys.exit(1) - - address = create_wallet(args.name, password) - print(f"Wallet address: {address}") - - elif args.command == "send": - # Get password - password = None - if args.password: - password = args.password - elif args.password_file: - with open(args.password_file) as f: - password = f.read().strip() - else: - import getpass - password = getpass.getpass(f"Enter password for wallet '{args.from_wallet}': ") - - if not password: - print("Error: Password is required") - sys.exit(1) - - tx_hash = send_transaction( - args.from_wallet, - args.to_address, - args.amount, - args.fee, - password, - rpc_url=args.rpc_url - ) - - if tx_hash: - print(f"Transaction hash: {tx_hash}") - else: - sys.exit(1) - - elif args.command == "list": - wallets = list_wallets() - - if args.format == "json": - print(json.dumps(wallets, indent=2)) - else: - print("Wallets:") - for wallet in wallets: - print(f" {wallet['name']}: {wallet['address']}") - - elif args.command == "balance": - balance_info = get_balance(args.name, rpc_url=args.rpc_url) - if balance_info: - print(f"Wallet: {balance_info['wallet_name']}") - print(f"Address: {balance_info['address']}") - print(f"Balance: {balance_info['balance']} AIT") - print(f"Nonce: {balance_info['nonce']}") - else: - sys.exit(1) - - elif args.command == "transactions": - transactions = get_transactions(args.name, limit=args.limit, rpc_url=args.rpc_url) - - if args.format == "json": - print(json.dumps(transactions, indent=2)) - else: - print(f"Transactions for {args.name}:") - for i, tx in enumerate(transactions, 1): - print(f" {i}. Hash: {tx.get('hash', 'N/A')}") - print(f" Amount: {tx.get('value', 0)} AIT") - print(f" Fee: {tx.get('fee', 0)} AIT") - print(f" Type: {tx.get('type', 'N/A')}") - print() - - elif args.command == "chain": - chain_info = get_chain_info(rpc_url=args.rpc_url) - if chain_info: - print("Blockchain Information:") - print(f" Chain ID: {chain_info.get('chain_id', 'N/A')}") - print(f" Supported Chains: {chain_info.get('supported_chains', 'N/A')}") - print(f" Height: {chain_info.get('height', 'N/A')}") - print(f" Latest Block: {str(chain_info.get('hash', 'N/A'))[:16]}...") - print(f" Proposer: {chain_info.get('proposer_id', 'N/A') or 'none'}") - else: - sys.exit(1) - - elif args.command == "network": - network_info = get_network_status(rpc_url=args.rpc_url) - if network_info: - print("Network Status:") - print(f" Height: {network_info.get('height', 'N/A')}") - print(f" Latest Block: {str(network_info.get('hash', 'N/A'))[:16]}...") - print(f" Chain ID: {network_info.get('chain_id', 'ait-mainnet')}") - print(f" Tx Count: {network_info.get('tx_count', 0)}") - print(f" Timestamp: {network_info.get('timestamp', 'N/A')}") - else: - sys.exit(1) - - elif args.command == "analytics": - analytics = get_blockchain_analytics(args.type, args.limit, rpc_url=args.rpc_url) - if analytics: - print(f"Blockchain Analytics ({analytics['type']}):") - for key, value in analytics.items(): - if key != "type": - print(f" {key.replace('_', ' ').title()}: {value}") - else: - sys.exit(1) - - elif args.command == "marketplace": - result = marketplace_operations(args.action, name=args.name, price=args.price, - description=args.description, wallet=args.wallet) - if result: - print(f"Marketplace {result['action']}:") - for key, value in result.items(): - if key != "action": - print(f" {key.replace('_', ' ').title()}: {value}") - else: - sys.exit(1) - - elif args.command == "ai-ops": - result = ai_operations(args.action, model=args.model, prompt=args.prompt, - job_id=args.job_id, wallet=args.wallet) - if result: - print(f"AI Operations {result['action']}:") - for key, value in result.items(): - if key != "action": - print(f" {key.replace('_', ' ').title()}: {value}") - else: - sys.exit(1) - - elif args.command == "mining": - result = mining_operations(args.action, wallet=args.wallet) - if result: - print(f"Mining {result['action']}:") - for key, value in result.items(): - if key != "action": - print(f" {key.replace('_', ' ').title()}: {value}") - else: - sys.exit(1) - - elif args.command == "agent": - # Only pass arguments that are defined for this subcommand - kwargs = {} - if hasattr(args, 'name') and args.name: - kwargs['name'] = args.name - if hasattr(args, 'description') and args.description: - kwargs['description'] = args.description - if hasattr(args, 'verification') and args.verification: - kwargs['verification'] = args.verification - if hasattr(args, 'max_execution_time') and args.max_execution_time: - kwargs['max_execution_time'] = args.max_execution_time - if hasattr(args, 'max_cost_budget') and args.max_cost_budget: - kwargs['max_cost_budget'] = args.max_cost_budget - if hasattr(args, 'input_data') and args.input_data: - kwargs['input_data'] = args.input_data - if hasattr(args, 'wallet') and args.wallet: - kwargs['wallet'] = args.wallet - if hasattr(args, 'priority') and args.priority: - kwargs['priority'] = args.priority - if hasattr(args, 'execution_id') and args.execution_id: - kwargs['execution_id'] = args.execution_id - if hasattr(args, 'status') and args.status: - kwargs['status'] = args.status - kwargs['chain_id'] = chain_id - - result = agent_operations(args.agent_action, **kwargs) - if result: - print(f"Agent {result['action']}:") - for key, value in result.items(): - if key != "action": - if isinstance(value, list): - print(f" {key.replace('_', ' ').title()}:") - for item in value: - print(f" - {item}") - else: - print(f" {key.replace('_', ' ').title()}: {value}") - else: - sys.exit(1) - - elif args.command == "hermes": - # Only pass arguments that are defined for this subcommand - kwargs = {} - if hasattr(args, 'agent_file') and args.agent_file: - kwargs['agent_file'] = args.agent_file - if hasattr(args, 'wallet') and args.wallet: - kwargs['wallet'] = args.wallet - if hasattr(args, 'environment') and args.environment: - kwargs['environment'] = args.environment - if hasattr(args, 'agent_id') and args.agent_id: - kwargs['agent_id'] = args.agent_id - if hasattr(args, 'metrics') and args.metrics: - kwargs['metrics'] = args.metrics - # Handle the market action parameter specifically - if hasattr(args, 'action') and args.action and args.hermes_action == 'market': - kwargs['market_action'] = args.action - if hasattr(args, 'price') and args.price: - kwargs['price'] = args.price - - result = hermes_operations(args.hermes_action, **kwargs) - if result: - print(f"hermes {result['action']}:") - for key, value in result.items(): - if key != "action": - if isinstance(value, list): - print(f" {key.replace('_', ' ').title()}:") - for item in value: - print(f" - {item}") - else: - print(f" {key.replace('_', ' ').title()}: {value}") - else: - sys.exit(1) - - elif args.command == "workflow": - # Only pass arguments that are defined for this subcommand - kwargs = {} - if hasattr(args, 'name') and args.name: - kwargs['name'] = args.name - if hasattr(args, 'template') and args.template: - kwargs['template'] = args.template - if hasattr(args, 'config_file') and args.config_file: - kwargs['config_file'] = args.config_file - if hasattr(args, 'params') and args.params: - kwargs['params'] = args.params - if hasattr(args, 'async_exec') and args.async_exec: - kwargs['async_exec'] = args.async_exec - - result = workflow_operations(args.workflow_action, **kwargs) - if result: - print(f"Workflow {result['action']}:") - for key, value in result.items(): - if key != "action": - print(f" {key.replace('_', ' ').title()}: {value}") - else: - sys.exit(1) - - elif args.command == "resource": - # Only pass arguments that are defined for this subcommand - kwargs = {} - if hasattr(args, 'type') and args.type: - kwargs['type'] = args.type - if hasattr(args, 'agent_id') and args.agent_id: - kwargs['agent_id'] = args.agent_id - if hasattr(args, 'cpu') and args.cpu: - kwargs['cpu'] = args.cpu - if hasattr(args, 'memory') and args.memory: - kwargs['memory'] = args.memory - if hasattr(args, 'duration') and args.duration: - kwargs['duration'] = args.duration - - result = resource_operations(args.resource_action, **kwargs) - if result: - print(f"Resource {result['action']}:") - for key, value in result.items(): - if key != "action": - print(f" {key.replace('_', ' ').title()}: {value}") - else: - sys.exit(1) - - elif args.command == "mine-start": - result = mining_operations('start', wallet=args.wallet) - if result: - print(f"Mining start:") - for key, value in result.items(): - if key != 'action': - print(f" {key.replace('_', ' ').title()}: {value}") - else: - sys.exit(1) - - elif args.command == "mine-stop": - result = mining_operations('stop') - if result: - print(f"Mining stop:") - for key, value in result.items(): - if key != 'action': - print(f" {key.replace('_', ' ').title()}: {value}") - else: - sys.exit(1) - - elif args.command == "mine-status": - result = mining_operations('status') - if result: - print(f"Mining status:") - for key, value in result.items(): - if key != 'action': - print(f" {key.replace('_', ' ').title()}: {value}") - else: - sys.exit(1) - - elif args.command == "market-list": - result = marketplace_operations('list', rpc_url=getattr(args, 'rpc_url', DEFAULT_RPC_URL)) - if result: - print(f"Marketplace listings:") - for key, value in result.items(): - if key != 'action': - if isinstance(value, list): - print(f" {key.replace('_', ' ').title()}:") - for item in value: - print(f" - {item}") - else: - print(f" {key.replace('_', ' ').title()}: {value}") - else: - print("No marketplace listings found.") - - elif args.command == "market-create": - result = marketplace_operations('create', name=getattr(args, 'type', ''), - price=args.price, description=args.description, - wallet=args.wallet) - if result: - print(f"Marketplace listing created:") - for key, value in result.items(): - if key != 'action': - print(f" {key.replace('_', ' ').title()}: {value}") - else: - sys.exit(1) - - elif args.command == "ai-submit": - result = ai_operations('submit', model=getattr(args, 'type', ''), - prompt=args.prompt, wallet=args.wallet) - if result: - print(f"AI job submitted:") - for key, value in result.items(): - if key != 'action': - print(f" {key.replace('_', ' ').title()}: {value}") - else: - sys.exit(1) - - elif args.command == "system": - if args.status: - print("System status: OK") - print(" Version: aitbc-cli v2.0.0") - print(" Services: Running") - print(" Nodes: 2 connected") - else: - print("System operation completed") - - elif args.command == "genesis": - import subprocess - import sys - from pathlib import Path - - script_path = Path("/opt/aitbc/apps/blockchain-node/scripts/unified_genesis.py") - - if not script_path.exists(): - print(f"Error: Genesis generation script not found: {script_path}") - sys.exit(1) - - if args.genesis_action == "init": - cmd = [sys.executable, str(script_path), "--chain-id", getattr(args, 'chain_id', 'ait-mainnet')] - - if hasattr(args, 'create_wallet') and args.create_wallet: - cmd.append("--create-wallet") - if hasattr(args, 'password') and args.password: - cmd.extend(["--password", args.password]) - if hasattr(args, 'proposer') and args.proposer: - cmd.extend(["--proposer", args.proposer]) - if hasattr(args, 'force') and args.force: - cmd.append("--force") - if hasattr(args, 'register_service') and args.register_service: - cmd.append("--register-service") - if hasattr(args, 'service_url') and args.service_url: - cmd.extend(["--service-url", args.service_url]) - - try: - result = subprocess.run(cmd, capture_output=True, text=True, check=True) - print(result.stdout) - if result.stderr: - print(result.stderr) - except subprocess.CalledProcessError as e: - print(f"Error: Genesis generation failed: {e.stderr}") - sys.exit(1) - - elif args.genesis_action == "verify": - import json - import sqlite3 - - chain_id = getattr(args, 'chain_id', 'ait-mainnet') - - # Check genesis config file - genesis_path = Path(f"/var/lib/aitbc/data/{chain_id}/genesis.json") - if not genesis_path.exists(): - print(f"Error: Genesis config not found: {genesis_path}") - sys.exit(1) - - try: - with open(genesis_path) as f: - genesis_data = json.load(f) - - print(f"✓ Genesis config found: {genesis_path}") - print(f" Chain ID: {genesis_data.get('chain_id')}") - print(f" Genesis Hash: {genesis_data.get('block', {}).get('hash')}") - print(f" Proposer: {genesis_data.get('block', {}).get('proposer')}") - print(f" Allocations: {len(genesis_data.get('allocations', []))}") - except Exception as e: - print(f"Error: Failed to read genesis config: {e}") - sys.exit(1) - - # Check database - db_path = Path("/var/lib/aitbc/data/chain.db") - if not db_path.exists(): - print(f"Error: Database not found: {db_path}") - sys.exit(1) - - try: - conn = sqlite3.connect(str(db_path)) - cursor = conn.cursor() - - cursor.execute("SELECT * FROM block WHERE height=0 AND chain_id=?", (chain_id,)) - genesis_block = cursor.fetchone() - - if genesis_block: - print(f"✓ Genesis block found in database") - print(f" Height: {genesis_block[1]}") - print(f" Hash: {genesis_block[2]}") - print(f" Proposer: {genesis_block[4]}") - else: - print(f"Error: Genesis block not found in database for chain {chain_id}") - - cursor.execute("SELECT COUNT(*) FROM account WHERE chain_id=?", (chain_id,)) - account_count = cursor.fetchone()[0] - - if account_count > 0: - print(f"✓ Found {account_count} accounts in database") - else: - print(f"Error: No accounts found in database for chain {chain_id}") - - conn.close() - except Exception as e: - print(f"Error: Failed to verify database: {e}") - sys.exit(1) - - # Check genesis wallet - wallet_path = Path("/var/lib/aitbc/keystore/genesis.json") - if wallet_path.exists(): - print(f"✓ Genesis wallet found: {wallet_path}") - try: - with open(wallet_path) as f: - wallet_data = json.load(f) - print(f" Address: {wallet_data.get('address')}") - print(f" Public Key: {wallet_data.get('public_key')[:16]}..." if wallet_data.get('public_key') else "N/A") - except Exception as e: - print(f"Error: Failed to read genesis wallet: {e}") - else: - print(f"Error: Genesis wallet not found: {wallet_path}") - - elif args.genesis_action == "info": - import json - - chain_id = getattr(args, 'chain_id', 'ait-mainnet') - genesis_path = Path(f"/var/lib/aitbc/data/{chain_id}/genesis.json") - - if not genesis_path.exists(): - print(f"Error: Genesis config not found: {genesis_path}") - sys.exit(1) - - try: - with open(genesis_path) as f: - genesis_data = json.load(f) - - block = genesis_data.get("block", {}) - allocations = genesis_data.get("allocations", []) - - print(f"Genesis Information for {chain_id}:") - print(f" Chain ID: {genesis_data.get('chain_id')}") - print(f" Block Height: {block.get('height')}") - print(f" Block Hash: {block.get('hash')}") - print(f" Parent Hash: {block.get('parent_hash')}") - print(f" Proposer: {block.get('proposer')}") - print(f" Timestamp: {block.get('timestamp')}") - print(f" Transaction Count: {block.get('tx_count')}") - print(f" Total Allocations: {len(allocations)}") - print(f"\n Top Allocations:") - for i, alloc in enumerate(allocations[:5], 1): - print(f" {i}. {alloc.get('address')}: {alloc.get('balance')} AIT") - - except Exception as e: - print(f"Error: Failed to read genesis info: {e}") - sys.exit(1) - - else: - print(f"Error: Unknown genesis action: {args.genesis_action}") - sys.exit(1) - - elif args.command == "blockchain": - rpc_url = getattr(args, 'rpc_url', DEFAULT_RPC_URL) - if args.blockchain_action == "info": - result = get_chain_info(rpc_url) - if result: - print("Blockchain information:") - for key, value in result.items(): - print(f" {key.replace('_', ' ').title()}: {value}") - else: - print("Blockchain info unavailable") - elif args.blockchain_action == "height": - result = get_chain_info(rpc_url) - if result and 'height' in result: - print(result['height']) - else: - print("0") - elif args.blockchain_action == "block": - if args.number: - print(f"Block #{args.number}:") - print(f" Hash: 0x{args.number:016x}") - print(f" Timestamp: $(date)") - print(f" Transactions: {args.number % 100}") - print(f" Gas used: {args.number * 1000}") - else: - print("Error: --number required") - sys.exit(1) - else: - print("Blockchain operation completed") - - elif args.command == "block": - if args.action == "info": - result = get_chain_info() - if result: - print("Block information:") - for key in ["height", "latest_block", "proposer"]: - if key in result: - print(f" {key.replace('_', ' ').title()}: {result[key]}") - else: - print("Block info unavailable") - - elif args.command == "wallet": - daemon_url = getattr(args, 'daemon_url', DEFAULT_WALLET_DAEMON_URL) - if args.wallet_action == "backup": - print(f"Wallet backup: {args.name}") - backup_path = get_data_path("backups") - print(f" Backup created: {backup_path}/{args.name}_$(date +%Y%m%d).json") - print(f" Status: completed") - elif args.wallet_action == "export": - print(f"Wallet export: {args.name}") - export_path = get_data_path("exports") - print(f" Export file: {export_path}/{args.name}_private.json") - print(f" Status: completed") - elif args.wallet_action == "sync": - if args.all: - print("Wallet sync: All wallets") - print(f" Sync status: completed") - print(f" Last sync: $(date)") - else: - print(f"Wallet sync: {args.name}") - print(f" Sync status: completed") - print(f" Last sync: $(date)") - elif args.wallet_action == "balance": - # Use wallet daemon for balance queries - if args.all: - try: - http_client = AITBCHTTPClient(base_url=daemon_url, timeout=5) - data = http_client.get("/v1/wallets") - wallet_list = data.get("items", data.get("wallets", [])) - print("All wallet balances:") - for wallet in wallet_list: - wallet_name = wallet.get("wallet_name", "unknown") - wallet_address = wallet.get("address", "") - # Query balance for each wallet - try: - balance_data = http_client.get(f"/v1/wallets/{wallet_name}/balance") - balance = balance_data.get("balance", 0) - print(f" {wallet_name}: {balance} AIT") - except NetworkError: - print(f" {wallet_name}: balance unavailable") - except Exception: - print(f" {wallet_name}: balance query failed") - except NetworkError as e: - print(f"Warning: Failed to query wallet daemon: {e}") - print("Falling back to mock balances:") - print(" genesis: 10000 AIT") - print(" aitbc1: 5000 AIT") - print(" hermes-trainee: 100 AIT") - except Exception as e: - print(f"Warning: Failed to query wallet daemon: {e}") - print("Falling back to mock balances:") - print(" genesis: 10000 AIT") - print(" aitbc1: 5000 AIT") - print(" hermes-trainee: 100 AIT") - elif args.name: - try: - http_client = AITBCHTTPClient(base_url=daemon_url, timeout=5) - balance_data = http_client.get(f"/v1/wallets/{args.name}/balance") - balance = balance_data.get("balance", 0) - print(f"Wallet: {args.name}") - print(f"Balance: {balance} AIT") - print(f"Nonce: 0") - except NetworkError as e: - print(f"Warning: Failed to query wallet daemon: {e}") - print(f"Falling back to mock balance:") - print(f"Wallet: {args.name}") - print(f"Address: ait1{args.name[:8]}...") - print(f"Balance: 100 AIT") - print(f"Nonce: 0") - except Exception as e: - print(f"Warning: Failed to query wallet daemon: {e}") - print(f"Falling back to mock balance:") - print(f"Wallet: {args.name}") - print(f"Address: ait1{args.name[:8]}...") - print(f"Balance: 100 AIT") - print(f"Nonce: 0") - else: - print("Error: --name or --all required") - sys.exit(1) - else: - print("Wallet operation completed") - - elif args.command == "wallet-backup": - print(f"Wallet backup: {args.name}") - backup_path = get_data_path("backups") - print(f" Backup created: {backup_path}/{args.name}_$(date +%Y%m%d).json") - print(f" Status: completed") - - elif args.command == "wallet-export": - print(f"Wallet export: {args.name}") - export_path = get_data_path("exports") - print(f" Export file: {export_path}/{args.name}_private.json") - print(f" Status: completed") - - elif args.command == "wallet-sync": - print(f"Wallet sync: {args.name}") - print(f" Sync status: completed") - print(f" Last sync: $(date)") - - elif args.command == "all-balances": - print("All wallet balances:") - print(" genesis: 10000 AIT") - print(" aitbc1: 5000 AIT") - print(" hermes-trainee: 100 AIT") - - elif args.command == "mining": - # Handle flag-based commands - if args.start: - print("Mining started:") - print(f" Wallet: {args.wallet or 'default'}") - print(f" Threads: 1") - print(f" Status: active") - elif args.stop: - print("Mining stopped:") - print(f" Status: stopped") - print(f" Blocks mined: 0") - elif args.status: - print("Mining status:") - print(f" Status: inactive") - print(f" Hash rate: 0 MH/s") - print(f" Blocks mined: 0") - print(f" Rewards: 0 AIT") - elif args.action: - # Use existing action-based implementation - result = mining_operations(args.action, wallet=args.wallet, rpc_url=getattr(args, 'rpc_url', DEFAULT_RPC_URL)) - if result: - print(f"Mining {args.action}:") - for key, value in result.items(): - if key != 'action': - print(f" {key.replace('_', ' ').title()}: {value}") - else: - sys.exit(1) - else: - print("Mining operation: Use --start, --stop, --status, or --action") - - elif args.command == "network": - rpc_url = getattr(args, 'rpc_url', DEFAULT_RPC_URL) - if args.network_action == "status": - print("Network status:") - print(" Connected nodes: 2") - print(" Genesis: healthy") - print(" Follower: healthy") - print(" Sync status: synchronized") - elif args.network_action == "peers": - print("Network peers:") - print(" - genesis (localhost:8006) - Connected") - follower_host = os.getenv("AITBC_FOLLOWER_HOST", "aitbc1") - follower_port = os.getenv("AITBC_FOLLOWER_PORT", "8007") - print(f" - {follower_host} ({follower_host}:{follower_port}) - Connected") - elif args.network_action == "sync": - if args.status: - print("Network sync status:") - print(" Status: synchronized") - print(" Block height: 22502") - print(" Last sync: $(date)") - else: - print("Network sync: Complete") - elif args.network_action == "ping": - node = args.node or "aitbc1" - print(f"Ping: Node {node} reachable") - print(f" Latency: 5ms") - print(f" Status: connected") - elif args.network_action == "propagate": - data = args.data or "test-data" - print(f"Data propagation: Complete") - print(f" Data: {data}") - print(f" Nodes: 2/2 updated") - else: - print("Network operation completed") - - elif args.command == "simulate": - if hasattr(args, 'simulate_command'): - if args.simulate_command == "blockchain": - simulate_blockchain(args.blocks, args.transactions, args.delay) - elif args.simulate_command == "wallets": - simulate_wallets(args.wallets, args.balance, args.transactions, args.amount_range) - elif args.simulate_command == "price": - simulate_price(args.price, args.volatility, args.timesteps, args.delay) - elif args.simulate_command == "network": - simulate_network(args.nodes, args.network_delay, args.failure_rate) - elif args.simulate_command == "ai-jobs": - simulate_ai_jobs(args.jobs, args.models, args.duration_range) - else: - print(f"Unknown simulate command: {args.simulate_command}") - sys.exit(1) - else: - pass - -# Click command groups (agent-specific operations) -CLICK_COMMANDS = [ - 'agent', 'ipfs', 'oracle', 'swarm', 'arbitrage', 'validator', - 'plugin', 'database', 'island', 'edge', 'ai', 'monitor', - 'governance', 'staking', 'compliance', 'cross-chain' -] - -def main(argv=None): - """Main entry point - delegates to Click CLI or unified CLI""" - if argv is None: - argv = sys.argv[1:] - - # Check if this is a Click command - if argv and argv[0] in CLICK_COMMANDS: - # Delegate to Click CLI by calling click_cli.py as a module - import subprocess - click_cli_path = Path("/opt/aitbc/cli/click_cli.py") - result = subprocess.run([sys.executable, str(click_cli_path)] + sys.argv[1:]) - return result.returncode - elif len(argv) > 0 and argv[0] == "genesis": - # Run genesis CLI subprocess - genesis_path = Path(__file__).parent.parent / "genesis" / "genesis_cli.py" - if genesis_path.exists(): - import subprocess - result = subprocess.run([sys.executable, str(genesis_path)] + argv[1:]) - return result.returncode - else: - print("Genesis CLI not found") - return 1 - else: - # Run unified CLI (parser/handler architecture) - from unified_cli import run_cli - return run_cli(argv, globals()) - -# Monkey-patch unified_cli to add Click commands to help output -def add_click_commands_to_help(): - """Add Click commands to unified_cli help for discoverability""" - try: - import unified_cli - original_run_cli = unified_cli.run_cli - - def patched_run_cli(argv, core): - # Check if --help is requested and no command provided - if argv and '--help' in argv or len(argv) == 0: - print("\nClick-based Commands (agent operations):") - print(" agent - AI agent workflow and execution management") - print(" ipfs - IPFS distributed storage commands") - print(" oracle - Oracle price discovery and management") - print(" swarm - Swarm intelligence and collective optimization") - print(" arbitrage - Market arbitrage and price analysis") - print(" validator - Staking validator management") - print(" plugin - Plugin marketplace and management") - print(" database - Database service commands") - print(" island - Island computing commands") - print(" edge - Edge computing commands") - print(" ai - AI marketplace commands") - print(" monitor - Monitoring, metrics, and alerting") - print(" governance- Governance proposals and voting") - print(" staking - Staking and validator management") - print(" compliance- Compliance and regulatory management") - print(" cross-chain (transfer, list, swaps)") - print() - return original_run_cli(argv, core) - - unified_cli.run_cli = patched_run_cli - except Exception: - pass - -# Apply monkey-patch on import -add_click_commands_to_help() - -if __name__ == "__main__": - sys.exit(main() or 0)