refactor: flatten CLI directory structure - remove 'box in a box'

BEFORE:
/opt/aitbc/cli/
├── aitbc_cli/                    # Python package (box in a box)
│   ├── commands/
│   ├── main.py
│   └── ...
├── setup.py

AFTER:
/opt/aitbc/cli/                    # Flat structure
├── commands/                      # Direct access
├── main.py                        # Direct access
├── auth/
├── config/
├── core/
├── models/
├── utils/
├── plugins.py
└── setup.py

CHANGES MADE:
- Moved all files from aitbc_cli/ to cli/ root
- Fixed all relative imports (from . to absolute imports)
- Updated setup.py entry point: aitbc_cli.main → main
- Added CLI directory to Python path in entry script
- Simplified deployment.py to remove dependency on deleted core.deployment
- Fixed import paths in all command files
- Recreated virtual environment with new structure

BENEFITS:
- Eliminated 'box in a box' nesting
- Simpler directory structure
- Direct access to all modules
- Cleaner imports
- Easier maintenance and development
- CLI works with both 'python main.py' and 'aitbc' commands
This commit is contained in:
2026-03-26 09:12:02 +01:00
parent b3cf7384ce
commit c0952c2525
89 changed files with 265 additions and 4605 deletions

3
cli/core/__init__.py Executable file
View File

@@ -0,0 +1,3 @@
"""
Core modules for multi-chain functionality
"""

524
cli/core/agent_communication.py Executable file
View File

@@ -0,0 +1,524 @@
"""
Cross-chain agent communication system
"""
import asyncio
import json
import hashlib
import time
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Any, Set
from dataclasses import dataclass, asdict
from enum import Enum
import uuid
from collections import defaultdict
from core.config import MultiChainConfig
from core.node_client import NodeClient
class MessageType(Enum):
"""Agent message types"""
DISCOVERY = "discovery"
ROUTING = "routing"
COMMUNICATION = "communication"
COLLABORATION = "collaboration"
PAYMENT = "payment"
REPUTATION = "reputation"
GOVERNANCE = "governance"
class AgentStatus(Enum):
"""Agent status"""
ACTIVE = "active"
INACTIVE = "inactive"
BUSY = "busy"
OFFLINE = "offline"
@dataclass
class AgentInfo:
"""Agent information"""
agent_id: str
name: str
chain_id: str
node_id: str
status: AgentStatus
capabilities: List[str]
reputation_score: float
last_seen: datetime
endpoint: str
version: str
@dataclass
class AgentMessage:
"""Agent communication message"""
message_id: str
sender_id: str
receiver_id: str
message_type: MessageType
chain_id: str
target_chain_id: Optional[str]
payload: Dict[str, Any]
timestamp: datetime
signature: str
priority: int
ttl_seconds: int
@dataclass
class AgentCollaboration:
"""Agent collaboration record"""
collaboration_id: str
agent_ids: List[str]
chain_ids: List[str]
collaboration_type: str
status: str
created_at: datetime
updated_at: datetime
shared_resources: Dict[str, Any]
governance_rules: Dict[str, Any]
@dataclass
class AgentReputation:
"""Agent reputation record"""
agent_id: str
chain_id: str
reputation_score: float
successful_interactions: int
failed_interactions: int
total_interactions: int
last_updated: datetime
feedback_scores: List[float]
class CrossChainAgentCommunication:
"""Cross-chain agent communication system"""
def __init__(self, config: MultiChainConfig):
self.config = config
self.agents: Dict[str, AgentInfo] = {}
self.messages: Dict[str, AgentMessage] = {}
self.collaborations: Dict[str, AgentCollaboration] = {}
self.reputations: Dict[str, AgentReputation] = {}
self.routing_table: Dict[str, List[str]] = {}
self.discovery_cache: Dict[str, List[AgentInfo]] = {}
self.message_queue: Dict[str, List[AgentMessage]] = defaultdict(list)
# Communication thresholds
self.thresholds = {
'max_message_size': 1048576, # 1MB
'max_ttl_seconds': 3600, # 1 hour
'max_queue_size': 1000,
'min_reputation_score': 0.5,
'max_collaboration_size': 10
}
async def register_agent(self, agent_info: AgentInfo) -> bool:
"""Register an agent in the cross-chain network"""
try:
# Validate agent info
if not self._validate_agent_info(agent_info):
return False
# Check if agent already exists
if agent_info.agent_id in self.agents:
# Update existing agent
self.agents[agent_info.agent_id] = agent_info
else:
# Register new agent
self.agents[agent_info.agent_id] = agent_info
# Initialize reputation
if agent_info.agent_id not in self.reputations:
self.reputations[agent_info.agent_id] = AgentReputation(
agent_id=agent_info.agent_id,
chain_id=agent_info.chain_id,
reputation_score=agent_info.reputation_score,
successful_interactions=0,
failed_interactions=0,
total_interactions=0,
last_updated=datetime.now(),
feedback_scores=[]
)
# Update routing table
self._update_routing_table(agent_info)
# Clear discovery cache
self.discovery_cache.clear()
return True
except Exception as e:
print(f"Error registering agent {agent_info.agent_id}: {e}")
return False
async def discover_agents(self, chain_id: str, capabilities: Optional[List[str]] = None) -> List[AgentInfo]:
"""Discover agents on a specific chain"""
cache_key = f"{chain_id}:{'_'.join(capabilities or [])}"
# Check cache first
if cache_key in self.discovery_cache:
cached_time = self.discovery_cache[cache_key][0].last_seen if self.discovery_cache[cache_key] else None
if cached_time and (datetime.now() - cached_time).seconds < 300: # 5 minute cache
return self.discovery_cache[cache_key]
# Discover agents from chain
agents = []
for agent_id, agent_info in self.agents.items():
if agent_info.chain_id == chain_id and agent_info.status == AgentStatus.ACTIVE:
if capabilities:
# Check if agent has required capabilities
if any(cap in agent_info.capabilities for cap in capabilities):
agents.append(agent_info)
else:
agents.append(agent_info)
# Cache results
self.discovery_cache[cache_key] = agents
return agents
async def send_message(self, message: AgentMessage) -> bool:
"""Send a message to an agent"""
try:
# Validate message
if not self._validate_message(message):
return False
# Check if receiver exists
if message.receiver_id not in self.agents:
return False
# Check receiver reputation
receiver_reputation = self.reputations.get(message.receiver_id)
if receiver_reputation and receiver_reputation.reputation_score < self.thresholds['min_reputation_score']:
return False
# Add message to queue
self.message_queue[message.receiver_id].append(message)
self.messages[message.message_id] = message
# Attempt immediate delivery
await self._deliver_message(message)
return True
except Exception as e:
print(f"Error sending message {message.message_id}: {e}")
return False
async def _deliver_message(self, message: AgentMessage) -> bool:
"""Deliver a message to the target agent"""
try:
receiver = self.agents.get(message.receiver_id)
if not receiver:
return False
# Check if receiver is on same chain
if message.chain_id == receiver.chain_id:
# Same chain delivery
return await self._deliver_same_chain(message, receiver)
else:
# Cross-chain delivery
return await self._deliver_cross_chain(message, receiver)
except Exception as e:
print(f"Error delivering message {message.message_id}: {e}")
return False
async def _deliver_same_chain(self, message: AgentMessage, receiver: AgentInfo) -> bool:
"""Deliver message on the same chain"""
try:
# Simulate message delivery
print(f"Delivering message {message.message_id} to agent {receiver.agent_id} on chain {message.chain_id}")
# Update agent status
receiver.last_seen = datetime.now()
self.agents[receiver.agent_id] = receiver
# Remove from queue
if message in self.message_queue[receiver.agent_id]:
self.message_queue[receiver.agent_id].remove(message)
return True
except Exception as e:
print(f"Error in same-chain delivery: {e}")
return False
async def _deliver_cross_chain(self, message: AgentMessage, receiver: AgentInfo) -> bool:
"""Deliver message across chains"""
try:
# Find bridge nodes
bridge_nodes = await self._find_bridge_nodes(message.chain_id, receiver.chain_id)
if not bridge_nodes:
return False
# Route through bridge nodes
for bridge_node in bridge_nodes:
try:
# Simulate cross-chain routing
print(f"Routing message {message.message_id} through bridge node {bridge_node}")
# Update routing table
if message.chain_id not in self.routing_table:
self.routing_table[message.chain_id] = []
if receiver.chain_id not in self.routing_table[message.chain_id]:
self.routing_table[message.chain_id].append(receiver.chain_id)
# Update agent status
receiver.last_seen = datetime.now()
self.agents[receiver.agent_id] = receiver
# Remove from queue
if message in self.message_queue[receiver.agent_id]:
self.message_queue[receiver.agent_id].remove(message)
return True
except Exception as e:
print(f"Error routing through bridge node {bridge_node}: {e}")
continue
return False
except Exception as e:
print(f"Error in cross-chain delivery: {e}")
return False
async def create_collaboration(self, agent_ids: List[str], collaboration_type: str, governance_rules: Dict[str, Any]) -> Optional[str]:
"""Create a multi-agent collaboration"""
try:
# Validate collaboration
if len(agent_ids) > self.thresholds['max_collaboration_size']:
return None
# Check if all agents exist and are active
active_agents = []
for agent_id in agent_ids:
agent = self.agents.get(agent_id)
if agent and agent.status == AgentStatus.ACTIVE:
active_agents.append(agent)
else:
return None
if len(active_agents) < 2:
return None
# Create collaboration
collaboration_id = str(uuid.uuid4())
chain_ids = list(set(agent.chain_id for agent in active_agents))
collaboration = AgentCollaboration(
collaboration_id=collaboration_id,
agent_ids=agent_ids,
chain_ids=chain_ids,
collaboration_type=collaboration_type,
status="active",
created_at=datetime.now(),
updated_at=datetime.now(),
shared_resources={},
governance_rules=governance_rules
)
self.collaborations[collaboration_id] = collaboration
# Notify all agents
for agent_id in agent_ids:
notification = AgentMessage(
message_id=str(uuid.uuid4()),
sender_id="system",
receiver_id=agent_id,
message_type=MessageType.COLLABORATION,
chain_id=active_agents[0].chain_id,
target_chain_id=None,
payload={
"action": "collaboration_created",
"collaboration_id": collaboration_id,
"collaboration_type": collaboration_type,
"participants": agent_ids
},
timestamp=datetime.now(),
signature="system_notification",
priority=5,
ttl_seconds=3600
)
await self.send_message(notification)
return collaboration_id
except Exception as e:
print(f"Error creating collaboration: {e}")
return None
async def update_reputation(self, agent_id: str, interaction_success: bool, feedback_score: Optional[float] = None) -> bool:
"""Update agent reputation"""
try:
reputation = self.reputations.get(agent_id)
if not reputation:
return False
# Update interaction counts
reputation.total_interactions += 1
if interaction_success:
reputation.successful_interactions += 1
else:
reputation.failed_interactions += 1
# Add feedback score if provided
if feedback_score is not None:
reputation.feedback_scores.append(feedback_score)
# Keep only last 50 feedback scores
reputation.feedback_scores = reputation.feedback_scores[-50:]
# Calculate new reputation score
success_rate = reputation.successful_interactions / reputation.total_interactions
feedback_avg = sum(reputation.feedback_scores) / len(reputation.feedback_scores) if reputation.feedback_scores else 0.5
# Weighted average: 70% success rate, 30% feedback
reputation.reputation_score = (success_rate * 0.7) + (feedback_avg * 0.3)
reputation.last_updated = datetime.now()
# Update agent info
if agent_id in self.agents:
self.agents[agent_id].reputation_score = reputation.reputation_score
return True
except Exception as e:
print(f"Error updating reputation for agent {agent_id}: {e}")
return False
async def get_agent_status(self, agent_id: str) -> Optional[Dict[str, Any]]:
"""Get comprehensive agent status"""
try:
agent = self.agents.get(agent_id)
if not agent:
return None
reputation = self.reputations.get(agent_id)
# Get message queue status
queue_size = len(self.message_queue.get(agent_id, []))
# Get active collaborations
active_collaborations = [
collab for collab in self.collaborations.values()
if agent_id in collab.agent_ids and collab.status == "active"
]
status = {
"agent_info": asdict(agent),
"reputation": asdict(reputation) if reputation else None,
"message_queue_size": queue_size,
"active_collaborations": len(active_collaborations),
"last_seen": agent.last_seen.isoformat(),
"status": agent.status.value
}
return status
except Exception as e:
print(f"Error getting agent status for {agent_id}: {e}")
return None
async def get_network_overview(self) -> Dict[str, Any]:
"""Get cross-chain network overview"""
try:
# Count agents by chain
agents_by_chain = defaultdict(int)
active_agents_by_chain = defaultdict(int)
for agent in self.agents.values():
agents_by_chain[agent.chain_id] += 1
if agent.status == AgentStatus.ACTIVE:
active_agents_by_chain[agent.chain_id] += 1
# Count collaborations by type
collaborations_by_type = defaultdict(int)
active_collaborations = 0
for collab in self.collaborations.values():
collaborations_by_type[collab.collaboration_type] += 1
if collab.status == "active":
active_collaborations += 1
# Message statistics
total_messages = len(self.messages)
queued_messages = sum(len(queue) for queue in self.message_queue.values())
# Reputation statistics
reputation_scores = [rep.reputation_score for rep in self.reputations.values()]
avg_reputation = sum(reputation_scores) / len(reputation_scores) if reputation_scores else 0
overview = {
"total_agents": len(self.agents),
"active_agents": len([a for a in self.agents.values() if a.status == AgentStatus.ACTIVE]),
"agents_by_chain": dict(agents_by_chain),
"active_agents_by_chain": dict(active_agents_by_chain),
"total_collaborations": len(self.collaborations),
"active_collaborations": active_collaborations,
"collaborations_by_type": dict(collaborations_by_type),
"total_messages": total_messages,
"queued_messages": queued_messages,
"average_reputation": avg_reputation,
"routing_table_size": len(self.routing_table),
"discovery_cache_size": len(self.discovery_cache)
}
return overview
except Exception as e:
print(f"Error getting network overview: {e}")
return {}
def _validate_agent_info(self, agent_info: AgentInfo) -> bool:
"""Validate agent information"""
if not agent_info.agent_id or not agent_info.chain_id:
return False
if agent_info.reputation_score < 0 or agent_info.reputation_score > 1:
return False
if not agent_info.capabilities:
return False
return True
def _validate_message(self, message: AgentMessage) -> bool:
"""Validate message"""
if not message.sender_id or not message.receiver_id:
return False
if message.ttl_seconds > self.thresholds['max_ttl_seconds']:
return False
if len(json.dumps(message.payload)) > self.thresholds['max_message_size']:
return False
return True
def _update_routing_table(self, agent_info: AgentInfo):
"""Update routing table with agent information"""
if agent_info.chain_id not in self.routing_table:
self.routing_table[agent_info.chain_id] = []
# Add agent to routing table
if agent_info.agent_id not in self.routing_table[agent_info.chain_id]:
self.routing_table[agent_info.chain_id].append(agent_info.agent_id)
async def _find_bridge_nodes(self, source_chain: str, target_chain: str) -> List[str]:
"""Find bridge nodes for cross-chain communication"""
# For now, return any node that has agents on both chains
bridge_nodes = []
for node_id, node_config in self.config.nodes.items():
try:
async with NodeClient(node_config) as client:
chains = await client.get_hosted_chains()
chain_ids = [chain.id for chain in chains]
if source_chain in chain_ids and target_chain in chain_ids:
bridge_nodes.append(node_id)
except Exception:
continue
return bridge_nodes

514
cli/core/analytics.py Executable file
View File

@@ -0,0 +1,514 @@
"""
Chain analytics and monitoring system
"""
import asyncio
import json
import time
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Any, Tuple
from dataclasses import dataclass, asdict
from collections import defaultdict, deque
import statistics
from core.config import MultiChainConfig
from core.node_client import NodeClient
from models.chain import ChainInfo, ChainType, ChainStatus
@dataclass
class ChainMetrics:
"""Chain performance metrics"""
chain_id: str
node_id: str
timestamp: datetime
block_height: int
tps: float
avg_block_time: float
gas_price: int
memory_usage_mb: float
disk_usage_mb: float
active_nodes: int
client_count: int
miner_count: int
agent_count: int
network_in_mb: float
network_out_mb: float
@dataclass
class ChainAlert:
"""Chain performance alert"""
chain_id: str
alert_type: str
severity: str
message: str
timestamp: datetime
threshold: float
current_value: float
@dataclass
class ChainPrediction:
"""Chain performance prediction"""
chain_id: str
metric: str
predicted_value: float
confidence: float
time_horizon_hours: int
created_at: datetime
class ChainAnalytics:
"""Advanced chain analytics and monitoring"""
def __init__(self, config: MultiChainConfig):
self.config = config
self.metrics_history: Dict[str, deque] = defaultdict(lambda: deque(maxlen=1000))
self.alerts: List[ChainAlert] = []
self.predictions: Dict[str, List[ChainPrediction]] = defaultdict(list)
self.health_scores: Dict[str, float] = {}
self.performance_benchmarks: Dict[str, Dict[str, float]] = {}
# Alert thresholds
self.thresholds = {
'tps_low': 1.0,
'tps_high': 100.0,
'block_time_high': 10.0,
'memory_usage_high': 80.0, # percentage
'disk_usage_high': 85.0, # percentage
'node_count_low': 1,
'client_count_low': 5
}
async def collect_metrics(self, chain_id: str, node_id: str) -> ChainMetrics:
"""Collect metrics for a specific chain"""
if node_id not in self.config.nodes:
raise ValueError(f"Node {node_id} not configured")
node_config = self.config.nodes[node_id]
try:
async with NodeClient(node_config) as client:
chain_stats = await client.get_chain_stats(chain_id)
node_info = await client.get_node_info()
metrics = ChainMetrics(
chain_id=chain_id,
node_id=node_id,
timestamp=datetime.now(),
block_height=chain_stats.get("block_height", 0),
tps=chain_stats.get("tps", 0.0),
avg_block_time=chain_stats.get("avg_block_time", 0.0),
gas_price=chain_stats.get("gas_price", 0),
memory_usage_mb=chain_stats.get("memory_usage_mb", 0.0),
disk_usage_mb=chain_stats.get("disk_usage_mb", 0.0),
active_nodes=chain_stats.get("active_nodes", 0),
client_count=chain_stats.get("client_count", 0),
miner_count=chain_stats.get("miner_count", 0),
agent_count=chain_stats.get("agent_count", 0),
network_in_mb=node_info.get("network_in_mb", 0.0),
network_out_mb=node_info.get("network_out_mb", 0.0)
)
# Store metrics history
self.metrics_history[chain_id].append(metrics)
# Check for alerts
await self._check_alerts(metrics)
# Update health score
self._calculate_health_score(chain_id)
return metrics
except Exception as e:
print(f"Error collecting metrics for chain {chain_id}: {e}")
raise
async def collect_all_metrics(self) -> Dict[str, List[ChainMetrics]]:
"""Collect metrics for all chains across all nodes"""
all_metrics = {}
tasks = []
for node_id, node_config in self.config.nodes.items():
async def get_node_metrics(nid):
try:
async with NodeClient(node_config) as client:
chains = await client.get_hosted_chains()
node_metrics = []
for chain in chains:
try:
metrics = await self.collect_metrics(chain.id, nid)
node_metrics.append(metrics)
except Exception as e:
print(f"Error getting metrics for chain {chain.id}: {e}")
return node_metrics
except Exception as e:
print(f"Error getting chains from node {nid}: {e}")
return []
tasks.append(get_node_metrics(node_id))
results = await asyncio.gather(*tasks)
for node_metrics in results:
for metrics in node_metrics:
if metrics.chain_id not in all_metrics:
all_metrics[metrics.chain_id] = []
all_metrics[metrics.chain_id].append(metrics)
return all_metrics
def get_chain_performance_summary(self, chain_id: str, hours: int = 24) -> Dict[str, Any]:
"""Get performance summary for a chain"""
if chain_id not in self.metrics_history:
return {}
# Filter metrics by time range
cutoff_time = datetime.now() - timedelta(hours=hours)
recent_metrics = [
m for m in self.metrics_history[chain_id]
if m.timestamp >= cutoff_time
]
if not recent_metrics:
return {}
# Calculate statistics
tps_values = [m.tps for m in recent_metrics]
block_time_values = [m.avg_block_time for m in recent_metrics]
gas_prices = [m.gas_price for m in recent_metrics]
summary = {
"chain_id": chain_id,
"time_range_hours": hours,
"data_points": len(recent_metrics),
"latest_metrics": asdict(recent_metrics[-1]),
"statistics": {
"tps": {
"avg": statistics.mean(tps_values),
"min": min(tps_values),
"max": max(tps_values),
"median": statistics.median(tps_values)
},
"block_time": {
"avg": statistics.mean(block_time_values),
"min": min(block_time_values),
"max": max(block_time_values),
"median": statistics.median(block_time_values)
},
"gas_price": {
"avg": statistics.mean(gas_prices),
"min": min(gas_prices),
"max": max(gas_prices),
"median": statistics.median(gas_prices)
}
},
"health_score": self.health_scores.get(chain_id, 0.0),
"active_alerts": len([a for a in self.alerts if a.chain_id == chain_id])
}
return summary
def get_cross_chain_analysis(self) -> Dict[str, Any]:
"""Analyze performance across all chains"""
if not self.metrics_history:
# Return mock data for testing
return {
"total_chains": 2,
"active_chains": 2,
"chains_by_type": {"ait-devnet": 1, "ait-testnet": 1},
"performance_comparison": {
"ait-devnet": {
"tps": 2.5,
"block_time": 8.5,
"health_score": 85.0
},
"ait-testnet": {
"tps": 1.8,
"block_time": 12.3,
"health_score": 72.0
}
},
"resource_usage": {
"total_memory_mb": 2048.0,
"total_disk_mb": 10240.0,
"total_clients": 25,
"total_agents": 8
},
"alerts_summary": {
"total_alerts": 2,
"critical_alerts": 0,
"warning_alerts": 2
}
}
analysis = {
"total_chains": len(self.metrics_history),
"active_chains": len([c for c in self.metrics_history.keys() if self.health_scores.get(c, 0) > 0.5]),
"chains_by_type": defaultdict(int),
"performance_comparison": {},
"resource_usage": {
"total_memory_mb": 0,
"total_disk_mb": 0,
"total_clients": 0,
"total_agents": 0
},
"alerts_summary": {
"total_alerts": len(self.alerts),
"critical_alerts": len([a for a in self.alerts if a.severity == "critical"]),
"warning_alerts": len([a for a in self.alerts if a.severity == "warning"])
}
}
# Analyze each chain
for chain_id, metrics in self.metrics_history.items():
if not metrics:
continue
latest = metrics[-1]
# Chain type analysis
# This would need chain info, using placeholder
analysis["chains_by_type"]["unknown"] += 1
# Performance comparison
analysis["performance_comparison"][chain_id] = {
"tps": latest.tps,
"block_time": latest.avg_block_time,
"health_score": self.health_scores.get(chain_id, 0.0)
}
# Resource usage
analysis["resource_usage"]["total_memory_mb"] += latest.memory_usage_mb
analysis["resource_usage"]["total_disk_mb"] += latest.disk_usage_mb
analysis["resource_usage"]["total_clients"] += latest.client_count
analysis["resource_usage"]["total_agents"] += latest.agent_count
return analysis
async def predict_chain_performance(self, chain_id: str, hours: int = 24) -> List[ChainPrediction]:
"""Predict chain performance using historical data"""
if chain_id not in self.metrics_history or len(self.metrics_history[chain_id]) < 10:
return []
metrics = list(self.metrics_history[chain_id])
predictions = []
# Simple linear regression for TPS prediction
tps_values = [m.tps for m in metrics]
if len(tps_values) >= 10:
# Calculate trend
recent_tps = tps_values[-5:]
older_tps = tps_values[-10:-5]
if len(recent_tps) > 0 and len(older_tps) > 0:
recent_avg = statistics.mean(recent_tps)
older_avg = statistics.mean(older_tps)
trend = (recent_avg - older_avg) / older_avg if older_avg > 0 else 0
predicted_tps = recent_avg * (1 + trend * (hours / 24))
confidence = max(0.1, 1.0 - abs(trend)) # Higher confidence for stable trends
predictions.append(ChainPrediction(
chain_id=chain_id,
metric="tps",
predicted_value=predicted_tps,
confidence=confidence,
time_horizon_hours=hours,
created_at=datetime.now()
))
# Memory usage prediction
memory_values = [m.memory_usage_mb for m in metrics]
if len(memory_values) >= 10:
recent_memory = memory_values[-5:]
older_memory = memory_values[-10:-5]
if len(recent_memory) > 0 and len(older_memory) > 0:
recent_avg = statistics.mean(recent_memory)
older_avg = statistics.mean(older_memory)
growth_rate = (recent_avg - older_avg) / older_avg if older_avg > 0 else 0
predicted_memory = recent_avg * (1 + growth_rate * (hours / 24))
confidence = max(0.1, 1.0 - abs(growth_rate))
predictions.append(ChainPrediction(
chain_id=chain_id,
metric="memory_usage_mb",
predicted_value=predicted_memory,
confidence=confidence,
time_horizon_hours=hours,
created_at=datetime.now()
))
# Store predictions
self.predictions[chain_id].extend(predictions)
return predictions
def get_optimization_recommendations(self, chain_id: str) -> List[Dict[str, Any]]:
"""Get optimization recommendations for a chain"""
recommendations = []
if chain_id not in self.metrics_history:
return recommendations
metrics = list(self.metrics_history[chain_id])
if not metrics:
return recommendations
latest = metrics[-1]
# TPS optimization
if latest.tps < self.thresholds['tps_low']:
recommendations.append({
"type": "performance",
"priority": "high",
"issue": "Low TPS",
"current_value": latest.tps,
"recommended_action": "Consider increasing block size or optimizing smart contracts",
"expected_improvement": "20-50% TPS increase"
})
# Block time optimization
if latest.avg_block_time > self.thresholds['block_time_high']:
recommendations.append({
"type": "performance",
"priority": "medium",
"issue": "High block time",
"current_value": latest.avg_block_time,
"recommended_action": "Optimize consensus parameters or increase validator count",
"expected_improvement": "30-60% block time reduction"
})
# Memory usage optimization
if latest.memory_usage_mb > 1000: # 1GB threshold
recommendations.append({
"type": "resource",
"priority": "medium",
"issue": "High memory usage",
"current_value": latest.memory_usage_mb,
"recommended_action": "Implement data pruning or increase node memory",
"expected_improvement": "40-70% memory usage reduction"
})
# Node count optimization
if latest.active_nodes < 3:
recommendations.append({
"type": "availability",
"priority": "high",
"issue": "Low node count",
"current_value": latest.active_nodes,
"recommended_action": "Add more nodes to improve network resilience",
"expected_improvement": "Improved fault tolerance and sync speed"
})
return recommendations
async def _check_alerts(self, metrics: ChainMetrics):
"""Check for performance alerts"""
alerts = []
# TPS alerts
if metrics.tps < self.thresholds['tps_low']:
alerts.append(ChainAlert(
chain_id=metrics.chain_id,
alert_type="tps_low",
severity="warning",
message=f"Low TPS detected: {metrics.tps:.2f}",
timestamp=metrics.timestamp,
threshold=self.thresholds['tps_low'],
current_value=metrics.tps
))
# Block time alerts
if metrics.avg_block_time > self.thresholds['block_time_high']:
alerts.append(ChainAlert(
chain_id=metrics.chain_id,
alert_type="block_time_high",
severity="warning",
message=f"High block time: {metrics.avg_block_time:.2f}s",
timestamp=metrics.timestamp,
threshold=self.thresholds['block_time_high'],
current_value=metrics.avg_block_time
))
# Memory usage alerts
if metrics.memory_usage_mb > 2000: # 2GB threshold
alerts.append(ChainAlert(
chain_id=metrics.chain_id,
alert_type="memory_high",
severity="critical",
message=f"High memory usage: {metrics.memory_usage_mb:.1f}MB",
timestamp=metrics.timestamp,
threshold=2000,
current_value=metrics.memory_usage_mb
))
# Node count alerts
if metrics.active_nodes < self.thresholds['node_count_low']:
alerts.append(ChainAlert(
chain_id=metrics.chain_id,
alert_type="node_count_low",
severity="critical",
message=f"Low node count: {metrics.active_nodes}",
timestamp=metrics.timestamp,
threshold=self.thresholds['node_count_low'],
current_value=metrics.active_nodes
))
# Add to alerts list
self.alerts.extend(alerts)
# Keep only recent alerts (last 24 hours)
cutoff_time = datetime.now() - timedelta(hours=24)
self.alerts = [a for a in self.alerts if a.timestamp >= cutoff_time]
def _calculate_health_score(self, chain_id: str):
"""Calculate health score for a chain"""
if chain_id not in self.metrics_history:
self.health_scores[chain_id] = 0.0
return
metrics = list(self.metrics_history[chain_id])
if not metrics:
self.health_scores[chain_id] = 0.0
return
latest = metrics[-1]
# Health score components (0-100)
tps_score = min(100, (latest.tps / 10) * 100) # 10 TPS = 100% score
block_time_score = max(0, 100 - (latest.avg_block_time - 5) * 10) # 5s = 100% score
node_score = min(100, (latest.active_nodes / 5) * 100) # 5 nodes = 100% score
memory_score = max(0, 100 - (latest.memory_usage_mb / 1000) * 50) # 1GB = 50% penalty
# Weighted average
health_score = (tps_score * 0.3 + block_time_score * 0.3 +
node_score * 0.3 + memory_score * 0.1)
self.health_scores[chain_id] = max(0, min(100, health_score))
def get_dashboard_data(self) -> Dict[str, Any]:
"""Get data for analytics dashboard"""
dashboard = {
"overview": self.get_cross_chain_analysis(),
"chain_summaries": {},
"alerts": [asdict(alert) for alert in self.alerts[-20:]], # Last 20 alerts
"predictions": {},
"recommendations": {}
}
# Chain summaries
for chain_id in self.metrics_history.keys():
dashboard["chain_summaries"][chain_id] = self.get_chain_performance_summary(chain_id, 24)
dashboard["recommendations"][chain_id] = self.get_optimization_recommendations(chain_id)
# Latest predictions
if chain_id in self.predictions:
dashboard["predictions"][chain_id] = [
asdict(pred) for pred in self.predictions[chain_id][-5:]
]
return dashboard

498
cli/core/chain_manager.py Executable file
View File

@@ -0,0 +1,498 @@
"""
Chain manager for multi-chain operations
"""
import asyncio
import hashlib
import json
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Any
from .config import MultiChainConfig, get_node_config
from .node_client import NodeClient
from models.chain import (
ChainConfig, ChainInfo, ChainType, ChainStatus,
GenesisBlock, ChainMigrationPlan, ChainMigrationResult,
ChainBackupResult, ChainRestoreResult
)
class ChainAlreadyExistsError(Exception):
"""Chain already exists error"""
pass
class ChainNotFoundError(Exception):
"""Chain not found error"""
pass
class NodeNotAvailableError(Exception):
"""Node not available error"""
pass
class ChainManager:
"""Multi-chain manager"""
def __init__(self, config: MultiChainConfig):
self.config = config
self._chain_cache: Dict[str, ChainInfo] = {}
self._node_clients: Dict[str, Any] = {}
async def list_chains(
self,
chain_type: Optional[ChainType] = None,
include_private: bool = False,
sort_by: str = "id"
) -> List[ChainInfo]:
"""List all available chains"""
chains = []
# Get chains from all available nodes
for node_id, node_config in self.config.nodes.items():
try:
node_chains = await self._get_node_chains(node_id)
for chain in node_chains:
# Filter private chains if not requested
if not include_private and chain.privacy.visibility == "private":
continue
# Filter by chain type if specified
if chain_type and chain.type != chain_type:
continue
chains.append(chain)
except Exception as e:
# Log error but continue with other nodes
print(f"Error getting chains from node {node_id}: {e}")
# Remove duplicates (same chain on multiple nodes)
unique_chains = {}
for chain in chains:
if chain.id not in unique_chains:
unique_chains[chain.id] = chain
chains = list(unique_chains.values())
# Sort chains
if sort_by == "id":
chains.sort(key=lambda x: x.id)
elif sort_by == "size":
chains.sort(key=lambda x: x.size_mb, reverse=True)
elif sort_by == "nodes":
chains.sort(key=lambda x: x.node_count, reverse=True)
elif sort_by == "created":
chains.sort(key=lambda x: x.created_at, reverse=True)
return chains
async def get_chain_info(self, chain_id: str, detailed: bool = False, metrics: bool = False) -> ChainInfo:
"""Get detailed information about a chain"""
# Check cache first
if chain_id in self._chain_cache:
chain_info = self._chain_cache[chain_id]
else:
# Get from node
chain_info = await self._find_chain_on_nodes(chain_id)
if not chain_info:
raise ChainNotFoundError(f"Chain {chain_id} not found")
# Cache the result
self._chain_cache[chain_id] = chain_info
# Add detailed information if requested
if detailed or metrics:
chain_info = await self._enrich_chain_info(chain_info)
return chain_info
async def create_chain(self, chain_config: ChainConfig, node_id: Optional[str] = None) -> str:
"""Create a new chain"""
# Generate chain ID
chain_id = self._generate_chain_id(chain_config)
# Check if chain already exists
try:
await self.get_chain_info(chain_id)
raise ChainAlreadyExistsError(f"Chain {chain_id} already exists")
except ChainNotFoundError:
pass # Chain doesn't exist, which is good
# Select node if not specified
if not node_id:
node_id = await self._select_best_node(chain_config)
# Validate node availability
if node_id not in self.config.nodes:
raise NodeNotAvailableError(f"Node {node_id} not configured")
# Create genesis block
genesis_block = await self._create_genesis_block(chain_config, chain_id)
# Create chain on node
await self._create_chain_on_node(node_id, genesis_block)
# Return chain ID
return chain_id
async def delete_chain(self, chain_id: str, force: bool = False) -> bool:
"""Delete a chain"""
chain_info = await self.get_chain_info(chain_id)
# Get all nodes hosting this chain
hosting_nodes = await self._get_chain_hosting_nodes(chain_id)
if not force and len(hosting_nodes) > 1:
raise ValueError(f"Chain {chain_id} is hosted on {len(hosting_nodes)} nodes. Use --force to delete.")
# Delete from all hosting nodes
success = True
for node_id in hosting_nodes:
try:
await self._delete_chain_from_node(node_id, chain_id)
except Exception as e:
print(f"Error deleting chain from node {node_id}: {e}")
success = False
# Remove from cache
if chain_id in self._chain_cache:
del self._chain_cache[chain_id]
return success
async def add_chain_to_node(self, chain_id: str, node_id: str) -> bool:
"""Add a chain to a node"""
# Validate node
if node_id not in self.config.nodes:
raise NodeNotAvailableError(f"Node {node_id} not configured")
# Get chain info
chain_info = await self.get_chain_info(chain_id)
# Add chain to node
try:
await self._add_chain_to_node(node_id, chain_info)
return True
except Exception as e:
print(f"Error adding chain to node: {e}")
return False
async def remove_chain_from_node(self, chain_id: str, node_id: str, migrate: bool = False) -> bool:
"""Remove a chain from a node"""
# Validate node
if node_id not in self.config.nodes:
raise NodeNotAvailableError(f"Node {node_id} not configured")
if migrate:
# Find alternative node
target_node = await self._find_alternative_node(chain_id, node_id)
if target_node:
# Migrate chain first
migration_result = await self.migrate_chain(chain_id, node_id, target_node)
if not migration_result.success:
return False
# Remove chain from node
try:
await self._remove_chain_from_node(node_id, chain_id)
return True
except Exception as e:
print(f"Error removing chain from node: {e}")
return False
async def migrate_chain(self, chain_id: str, from_node: str, to_node: str, dry_run: bool = False) -> ChainMigrationResult:
"""Migrate a chain between nodes"""
# Validate nodes
if from_node not in self.config.nodes:
raise NodeNotAvailableError(f"Source node {from_node} not configured")
if to_node not in self.config.nodes:
raise NodeNotAvailableError(f"Target node {to_node} not configured")
# Get chain info
chain_info = await self.get_chain_info(chain_id)
# Create migration plan
migration_plan = await self._create_migration_plan(chain_id, from_node, to_node, chain_info)
if dry_run:
return ChainMigrationResult(
chain_id=chain_id,
source_node=from_node,
target_node=to_node,
success=migration_plan.feasible,
blocks_transferred=0,
transfer_time_seconds=0,
verification_passed=False,
error=None if migration_plan.feasible else "Migration not feasible"
)
if not migration_plan.feasible:
return ChainMigrationResult(
chain_id=chain_id,
source_node=from_node,
target_node=to_node,
success=False,
blocks_transferred=0,
transfer_time_seconds=0,
verification_passed=False,
error="; ".join(migration_plan.issues)
)
# Execute migration
return await self._execute_migration(chain_id, from_node, to_node)
async def backup_chain(self, chain_id: str, backup_path: Optional[str] = None, compress: bool = False, verify: bool = False) -> ChainBackupResult:
"""Backup a chain"""
# Get chain info
chain_info = await self.get_chain_info(chain_id)
# Get hosting node
hosting_nodes = await self._get_chain_hosting_nodes(chain_id)
if not hosting_nodes:
raise ChainNotFoundError(f"Chain {chain_id} not found on any node")
node_id = hosting_nodes[0] # Use first available node
# Set backup path
if not backup_path:
backup_path = self.config.chains.backup_path / f"{chain_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.tar.gz"
# Execute backup
return await self._execute_backup(chain_id, node_id, backup_path, compress, verify)
async def restore_chain(self, backup_file: str, node_id: Optional[str] = None, verify: bool = False) -> ChainRestoreResult:
"""Restore a chain from backup"""
backup_path = Path(backup_file)
if not backup_path.exists():
raise FileNotFoundError(f"Backup file {backup_file} not found")
# Select node if not specified
if not node_id:
node_id = await self._select_best_node_for_restore()
# Execute restore
return await self._execute_restore(backup_path, node_id, verify)
# Private methods
def _generate_chain_id(self, chain_config: ChainConfig) -> str:
"""Generate a unique chain ID"""
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
prefix = f"AITBC-{chain_config.type.value.upper()}-{chain_config.purpose.upper()}"
return f"{prefix}-{timestamp}"
async def _get_node_chains(self, node_id: str) -> List[ChainInfo]:
"""Get chains from a specific node"""
if node_id not in self.config.nodes:
return []
node_config = self.config.nodes[node_id]
try:
async with NodeClient(node_config) as client:
return await client.get_hosted_chains()
except Exception as e:
print(f"Error getting chains from node {node_id}: {e}")
return []
async def _find_chain_on_nodes(self, chain_id: str) -> Optional[ChainInfo]:
"""Find a chain on available nodes"""
for node_id in self.config.nodes:
try:
chains = await self._get_node_chains(node_id)
for chain in chains:
if chain.id == chain_id:
return chain
except Exception:
continue
return None
async def _enrich_chain_info(self, chain_info: ChainInfo) -> ChainInfo:
"""Enrich chain info with detailed data"""
# This would get additional metrics and detailed information
# For now, return the same chain info
return chain_info
async def _select_best_node(self, chain_config: ChainConfig) -> str:
"""Select the best node for creating a chain"""
# Simple selection - in reality, this would consider load, resources, etc.
available_nodes = list(self.config.nodes.keys())
if not available_nodes:
raise NodeNotAvailableError("No nodes available")
return available_nodes[0]
async def _create_genesis_block(self, chain_config: ChainConfig, chain_id: str) -> GenesisBlock:
"""Create a genesis block for the chain"""
timestamp = datetime.now()
# Create state root (placeholder)
state_data = {
"chain_id": chain_id,
"config": chain_config.dict(),
"timestamp": timestamp.isoformat()
}
state_root = hashlib.sha256(json.dumps(state_data, sort_keys=True).encode()).hexdigest()
# Create genesis hash
genesis_data = {
"chain_id": chain_id,
"timestamp": timestamp.isoformat(),
"state_root": state_root
}
genesis_hash = hashlib.sha256(json.dumps(genesis_data, sort_keys=True).encode()).hexdigest()
return GenesisBlock(
chain_id=chain_id,
chain_type=chain_config.type,
purpose=chain_config.purpose,
name=chain_config.name,
description=chain_config.description,
timestamp=timestamp,
consensus=chain_config.consensus,
privacy=chain_config.privacy,
parameters=chain_config.parameters,
state_root=state_root,
hash=genesis_hash
)
async def _create_chain_on_node(self, node_id: str, genesis_block: GenesisBlock) -> None:
"""Create a chain on a specific node"""
if node_id not in self.config.nodes:
raise NodeNotAvailableError(f"Node {node_id} not configured")
node_config = self.config.nodes[node_id]
try:
async with NodeClient(node_config) as client:
chain_id = await client.create_chain(genesis_block.dict())
print(f"Successfully created chain {chain_id} on node {node_id}")
except Exception as e:
print(f"Error creating chain on node {node_id}: {e}")
raise
async def _get_chain_hosting_nodes(self, chain_id: str) -> List[str]:
"""Get all nodes hosting a specific chain"""
hosting_nodes = []
for node_id in self.config.nodes:
try:
chains = await self._get_node_chains(node_id)
if any(chain.id == chain_id for chain in chains):
hosting_nodes.append(node_id)
except Exception:
continue
return hosting_nodes
async def _delete_chain_from_node(self, node_id: str, chain_id: str) -> None:
"""Delete a chain from a specific node"""
if node_id not in self.config.nodes:
raise NodeNotAvailableError(f"Node {node_id} not configured")
node_config = self.config.nodes[node_id]
try:
async with NodeClient(node_config) as client:
success = await client.delete_chain(chain_id)
if success:
print(f"Successfully deleted chain {chain_id} from node {node_id}")
else:
raise Exception(f"Failed to delete chain {chain_id}")
except Exception as e:
print(f"Error deleting chain from node {node_id}: {e}")
raise
async def _add_chain_to_node(self, node_id: str, chain_info: ChainInfo) -> None:
"""Add a chain to a specific node"""
# This would actually add the chain to the node
print(f"Adding chain {chain_info.id} to node {node_id}")
async def _remove_chain_from_node(self, node_id: str, chain_id: str) -> None:
"""Remove a chain from a specific node"""
# This would actually remove the chain from the node
print(f"Removing chain {chain_id} from node {node_id}")
async def _find_alternative_node(self, chain_id: str, exclude_node: str) -> Optional[str]:
"""Find an alternative node for a chain"""
hosting_nodes = await self._get_chain_hosting_nodes(chain_id)
for node_id in hosting_nodes:
if node_id != exclude_node:
return node_id
return None
async def _create_migration_plan(self, chain_id: str, from_node: str, to_node: str, chain_info: ChainInfo) -> ChainMigrationPlan:
"""Create a migration plan"""
# This would analyze the migration and create a detailed plan
return ChainMigrationPlan(
chain_id=chain_id,
source_node=from_node,
target_node=to_node,
size_mb=chain_info.size_mb,
estimated_minutes=int(chain_info.size_mb / 100), # Rough estimate
required_space_mb=chain_info.size_mb * 1.5, # 50% extra space
available_space_mb=10000, # Placeholder
feasible=True,
issues=[]
)
async def _execute_migration(self, chain_id: str, from_node: str, to_node: str) -> ChainMigrationResult:
"""Execute the actual migration"""
# This would actually execute the migration
print(f"Migrating chain {chain_id} from {from_node} to {to_node}")
return ChainMigrationResult(
chain_id=chain_id,
source_node=from_node,
target_node=to_node,
success=True,
blocks_transferred=1000, # Placeholder
transfer_time_seconds=300, # Placeholder
verification_passed=True
)
async def _execute_backup(self, chain_id: str, node_id: str, backup_path: str, compress: bool, verify: bool) -> ChainBackupResult:
"""Execute the actual backup"""
if node_id not in self.config.nodes:
raise NodeNotAvailableError(f"Node {node_id} not configured")
node_config = self.config.nodes[node_id]
try:
async with NodeClient(node_config) as client:
backup_info = await client.backup_chain(chain_id, backup_path)
return ChainBackupResult(
chain_id=chain_id,
backup_file=backup_info["backup_file"],
original_size_mb=backup_info["original_size_mb"],
backup_size_mb=backup_info["backup_size_mb"],
compression_ratio=backup_info["original_size_mb"] / backup_info["backup_size_mb"],
checksum=backup_info["checksum"],
verification_passed=verify
)
except Exception as e:
print(f"Error during backup: {e}")
raise
async def _execute_restore(self, backup_path: str, node_id: str, verify: bool) -> ChainRestoreResult:
"""Execute the actual restore"""
if node_id not in self.config.nodes:
raise NodeNotAvailableError(f"Node {node_id} not configured")
node_config = self.config.nodes[node_id]
try:
async with NodeClient(node_config) as client:
restore_info = await client.restore_chain(backup_path)
return ChainRestoreResult(
chain_id=restore_info["chain_id"],
node_id=node_id,
blocks_restored=restore_info["blocks_restored"],
verification_passed=restore_info["verification_passed"]
)
except Exception as e:
print(f"Error during restore: {e}")
raise
async def _select_best_node_for_restore(self) -> str:
"""Select the best node for restoring a chain"""
available_nodes = list(self.config.nodes.keys())
if not available_nodes:
raise NodeNotAvailableError("No nodes available")
return available_nodes[0]

101
cli/core/config.py Executable file
View File

@@ -0,0 +1,101 @@
"""
Multi-chain configuration management for AITBC CLI
"""
from pathlib import Path
from typing import Dict, Any, Optional
import yaml
from pydantic import BaseModel, Field
class NodeConfig(BaseModel):
"""Configuration for a specific node"""
id: str = Field(..., description="Node identifier")
endpoint: str = Field(..., description="Node endpoint URL")
timeout: int = Field(default=30, description="Request timeout in seconds")
retry_count: int = Field(default=3, description="Number of retry attempts")
max_connections: int = Field(default=10, description="Maximum concurrent connections")
class ChainConfig(BaseModel):
"""Default chain configuration"""
default_gas_limit: int = Field(default=10000000, description="Default gas limit")
default_gas_price: int = Field(default=20000000000, description="Default gas price in wei")
max_block_size: int = Field(default=1048576, description="Maximum block size in bytes")
backup_path: Path = Field(default=Path("./backups"), description="Backup directory path")
max_concurrent_chains: int = Field(default=100, description="Maximum concurrent chains per node")
class MultiChainConfig(BaseModel):
"""Multi-chain configuration"""
nodes: Dict[str, NodeConfig] = Field(default_factory=dict, description="Node configurations")
chains: ChainConfig = Field(default_factory=ChainConfig, description="Chain configuration")
logging_level: str = Field(default="INFO", description="Logging level")
enable_caching: bool = Field(default=True, description="Enable response caching")
cache_ttl: int = Field(default=300, description="Cache TTL in seconds")
def load_multichain_config(config_path: Optional[str] = None) -> MultiChainConfig:
"""Load multi-chain configuration from file"""
if config_path is None:
config_path = Path.home() / ".aitbc" / "multichain_config.yaml"
config_file = Path(config_path)
if not config_file.exists():
# Create default configuration
default_config = MultiChainConfig()
save_multichain_config(default_config, config_path)
return default_config
try:
with open(config_file, 'r') as f:
config_data = yaml.safe_load(f)
return MultiChainConfig(**config_data)
except Exception as e:
raise ValueError(f"Failed to load configuration from {config_path}: {e}")
def save_multichain_config(config: MultiChainConfig, config_path: Optional[str] = None) -> None:
"""Save multi-chain configuration to file"""
if config_path is None:
config_path = Path.home() / ".aitbc" / "multichain_config.yaml"
config_file = Path(config_path)
config_file.parent.mkdir(parents=True, exist_ok=True)
try:
# Convert Path objects to strings for YAML serialization
config_dict = config.dict()
if 'chains' in config_dict and 'backup_path' in config_dict['chains']:
config_dict['chains']['backup_path'] = str(config_dict['chains']['backup_path'])
with open(config_file, 'w') as f:
yaml.dump(config_dict, f, default_flow_style=False, indent=2)
except Exception as e:
raise ValueError(f"Failed to save configuration to {config_path}: {e}")
def get_default_node_config() -> NodeConfig:
"""Get default node configuration for local development"""
return NodeConfig(
id="default-node",
endpoint="http://localhost:8545",
timeout=30,
retry_count=3,
max_connections=10
)
def add_node_config(config: MultiChainConfig, node_config: NodeConfig) -> MultiChainConfig:
"""Add a node configuration"""
config.nodes[node_config.id] = node_config
return config
def remove_node_config(config: MultiChainConfig, node_id: str) -> MultiChainConfig:
"""Remove a node configuration"""
if node_id in config.nodes:
del config.nodes[node_id]
return config
def get_node_config(config: MultiChainConfig, node_id: str) -> Optional[NodeConfig]:
"""Get a specific node configuration"""
return config.nodes.get(node_id)
def list_node_configs(config: MultiChainConfig) -> Dict[str, NodeConfig]:
"""List all node configurations"""
return config.nodes.copy()

361
cli/core/genesis_generator.py Executable file
View File

@@ -0,0 +1,361 @@
"""
Genesis block generator for multi-chain functionality
"""
import hashlib
import json
import yaml
from datetime import datetime
from pathlib import Path
from typing import Dict, Any, Optional
from core.config import MultiChainConfig
from models.chain import GenesisBlock, GenesisConfig, ChainType, ConsensusAlgorithm
class GenesisValidationError(Exception):
"""Genesis validation error"""
pass
class GenesisGenerator:
"""Genesis block generator"""
def __init__(self, config: MultiChainConfig):
self.config = config
self.templates_dir = Path(__file__).parent.parent.parent / "templates" / "genesis"
def create_genesis(self, genesis_config: GenesisConfig) -> GenesisBlock:
"""Create a genesis block from configuration"""
# Validate configuration
self._validate_genesis_config(genesis_config)
# Generate chain ID if not provided
if not genesis_config.chain_id:
genesis_config.chain_id = self._generate_chain_id(genesis_config)
# Set timestamp if not provided
if not genesis_config.timestamp:
genesis_config.timestamp = datetime.now()
# Calculate state root
state_root = self._calculate_state_root(genesis_config)
# Calculate genesis hash
genesis_hash = self._calculate_genesis_hash(genesis_config, state_root)
# Create genesis block
genesis_block = GenesisBlock(
chain_id=genesis_config.chain_id,
chain_type=genesis_config.chain_type,
purpose=genesis_config.purpose,
name=genesis_config.name,
description=genesis_config.description,
timestamp=genesis_config.timestamp,
parent_hash=genesis_config.parent_hash,
gas_limit=genesis_config.gas_limit,
gas_price=genesis_config.gas_price,
difficulty=genesis_config.difficulty,
block_time=genesis_config.block_time,
accounts=genesis_config.accounts,
contracts=genesis_config.contracts,
consensus=genesis_config.consensus,
privacy=genesis_config.privacy,
parameters=genesis_config.parameters,
state_root=state_root,
hash=genesis_hash
)
return genesis_block
def create_from_template(self, template_name: str, custom_config_file: str) -> GenesisBlock:
"""Create genesis block from template"""
# Load template
template_path = self.templates_dir / f"{template_name}.yaml"
if not template_path.exists():
raise ValueError(f"Template {template_name} not found at {template_path}")
with open(template_path, 'r') as f:
template_data = yaml.safe_load(f)
# Load custom configuration
with open(custom_config_file, 'r') as f:
custom_data = yaml.safe_load(f)
# Merge template with custom config
merged_config = self._merge_configs(template_data, custom_data)
# Create genesis config
genesis_config = GenesisConfig(**merged_config['genesis'])
# Create genesis block
return self.create_genesis(genesis_config)
def validate_genesis(self, genesis_block: GenesisBlock) -> 'ValidationResult':
"""Validate a genesis block"""
errors = []
checks = {}
# Check required fields
checks['chain_id'] = bool(genesis_block.chain_id)
if not genesis_block.chain_id:
errors.append("Chain ID is required")
checks['chain_type'] = genesis_block.chain_type in ChainType
if genesis_block.chain_type not in ChainType:
errors.append(f"Invalid chain type: {genesis_block.chain_type}")
checks['purpose'] = bool(genesis_block.purpose)
if not genesis_block.purpose:
errors.append("Purpose is required")
checks['name'] = bool(genesis_block.name)
if not genesis_block.name:
errors.append("Name is required")
checks['timestamp'] = isinstance(genesis_block.timestamp, datetime)
if not isinstance(genesis_block.timestamp, datetime):
errors.append("Invalid timestamp format")
checks['consensus'] = bool(genesis_block.consensus)
if not genesis_block.consensus:
errors.append("Consensus configuration is required")
checks['hash'] = bool(genesis_block.hash)
if not genesis_block.hash:
errors.append("Genesis hash is required")
# Validate hash
if genesis_block.hash:
calculated_hash = self._calculate_genesis_hash(genesis_block, genesis_block.state_root)
checks['hash_valid'] = genesis_block.hash == calculated_hash
if genesis_block.hash != calculated_hash:
errors.append("Genesis hash does not match calculated hash")
# Validate state root
if genesis_block.state_root:
calculated_state_root = self._calculate_state_root_from_block(genesis_block)
checks['state_root_valid'] = genesis_block.state_root == calculated_state_root
if genesis_block.state_root != calculated_state_root:
errors.append("State root does not match calculated state root")
# Validate accounts
checks['accounts_valid'] = all(
bool(account.address) and bool(account.balance)
for account in genesis_block.accounts
)
if not checks['accounts_valid']:
errors.append("All accounts must have address and balance")
# Validate contracts
checks['contracts_valid'] = all(
bool(contract.name) and bool(contract.address) and bool(contract.bytecode)
for contract in genesis_block.contracts
)
if not checks['contracts_valid']:
errors.append("All contracts must have name, address, and bytecode")
# Validate consensus
if genesis_block.consensus:
checks['consensus_algorithm'] = genesis_block.consensus.algorithm in ConsensusAlgorithm
if genesis_block.consensus.algorithm not in ConsensusAlgorithm:
errors.append(f"Invalid consensus algorithm: {genesis_block.consensus.algorithm}")
return ValidationResult(
is_valid=len(errors) == 0,
errors=errors,
checks=checks
)
def get_genesis_info(self, genesis_file: str) -> Dict[str, Any]:
"""Get information about a genesis block file"""
genesis_path = Path(genesis_file)
if not genesis_path.exists():
raise FileNotFoundError(f"Genesis file {genesis_file} not found")
# Load genesis block
if genesis_path.suffix.lower() in ['.yaml', '.yml']:
with open(genesis_path, 'r') as f:
genesis_data = yaml.safe_load(f)
else:
with open(genesis_path, 'r') as f:
genesis_data = json.load(f)
genesis_block = GenesisBlock(**genesis_data)
return {
"chain_id": genesis_block.chain_id,
"chain_type": genesis_block.chain_type.value,
"purpose": genesis_block.purpose,
"name": genesis_block.name,
"description": genesis_block.description,
"created": genesis_block.timestamp.isoformat(),
"genesis_hash": genesis_block.hash,
"state_root": genesis_block.state_root,
"consensus_algorithm": genesis_block.consensus.algorithm.value,
"block_time": genesis_block.block_time,
"gas_limit": genesis_block.gas_limit,
"gas_price": genesis_block.gas_price,
"accounts_count": len(genesis_block.accounts),
"contracts_count": len(genesis_block.contracts),
"privacy_visibility": genesis_block.privacy.visibility,
"access_control": genesis_block.privacy.access_control,
"file_size": genesis_path.stat().st_size,
"file_format": genesis_path.suffix.lower().replace('.', '')
}
def export_genesis(self, chain_id: str, format: str = "json") -> str:
"""Export genesis block in specified format"""
# This would get the genesis block from storage
# For now, return placeholder
return f"Genesis block for {chain_id} in {format} format"
def calculate_genesis_hash(self, genesis_file: str) -> str:
"""Calculate genesis hash from file"""
genesis_path = Path(genesis_file)
if not genesis_path.exists():
raise FileNotFoundError(f"Genesis file {genesis_file} not found")
# Load genesis block
if genesis_path.suffix.lower() in ['.yaml', '.yml']:
with open(genesis_path, 'r') as f:
genesis_data = yaml.safe_load(f)
else:
with open(genesis_path, 'r') as f:
genesis_data = json.load(f)
genesis_block = GenesisBlock(**genesis_data)
return self._calculate_genesis_hash(genesis_block, genesis_block.state_root)
def list_templates(self) -> Dict[str, Dict[str, Any]]:
"""List available genesis templates"""
templates = {}
if not self.templates_dir.exists():
return templates
for template_file in self.templates_dir.glob("*.yaml"):
template_name = template_file.stem
try:
with open(template_file, 'r') as f:
template_data = yaml.safe_load(f)
templates[template_name] = {
"name": template_name,
"description": template_data.get('description', ''),
"chain_type": template_data.get('genesis', {}).get('chain_type', 'unknown'),
"purpose": template_data.get('genesis', {}).get('purpose', 'unknown'),
"file_path": str(template_file)
}
except Exception as e:
templates[template_name] = {
"name": template_name,
"description": f"Error loading template: {e}",
"chain_type": "error",
"purpose": "error",
"file_path": str(template_file)
}
return templates
# Private methods
def _validate_genesis_config(self, genesis_config: GenesisConfig) -> None:
"""Validate genesis configuration"""
if not genesis_config.chain_type:
raise GenesisValidationError("Chain type is required")
if not genesis_config.purpose:
raise GenesisValidationError("Purpose is required")
if not genesis_config.name:
raise GenesisValidationError("Name is required")
if not genesis_config.consensus:
raise GenesisValidationError("Consensus configuration is required")
if genesis_config.consensus.algorithm not in ConsensusAlgorithm:
raise GenesisValidationError(f"Invalid consensus algorithm: {genesis_config.consensus.algorithm}")
def _generate_chain_id(self, genesis_config: GenesisConfig) -> str:
"""Generate a unique chain ID"""
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
prefix = f"AITBC-{genesis_config.chain_type.value.upper()}-{genesis_config.purpose.upper()}"
return f"{prefix}-{timestamp}"
def _calculate_state_root(self, genesis_config: GenesisConfig) -> str:
"""Calculate state root hash"""
state_data = {
"chain_id": genesis_config.chain_id,
"chain_type": genesis_config.chain_type.value,
"purpose": genesis_config.purpose,
"name": genesis_config.name,
"timestamp": genesis_config.timestamp.isoformat() if genesis_config.timestamp else datetime.now().isoformat(),
"accounts": [account.dict() for account in genesis_config.accounts],
"contracts": [contract.dict() for contract in genesis_config.contracts],
"parameters": genesis_config.parameters.dict()
}
state_json = json.dumps(state_data, sort_keys=True)
return hashlib.sha256(state_json.encode()).hexdigest()
def _calculate_genesis_hash(self, genesis_config: GenesisConfig, state_root: str) -> str:
"""Calculate genesis block hash"""
genesis_data = {
"chain_id": genesis_config.chain_id,
"chain_type": genesis_config.chain_type.value,
"purpose": genesis_config.purpose,
"name": genesis_config.name,
"timestamp": genesis_config.timestamp.isoformat() if genesis_config.timestamp else datetime.now().isoformat(),
"parent_hash": genesis_config.parent_hash,
"gas_limit": genesis_config.gas_limit,
"gas_price": genesis_config.gas_price,
"difficulty": genesis_config.difficulty,
"block_time": genesis_config.block_time,
"consensus": genesis_config.consensus.dict(),
"privacy": genesis_config.privacy.dict(),
"parameters": genesis_config.parameters.dict(),
"state_root": state_root
}
genesis_json = json.dumps(genesis_data, sort_keys=True)
return hashlib.sha256(genesis_json.encode()).hexdigest()
def _calculate_state_root_from_block(self, genesis_block: GenesisBlock) -> str:
"""Calculate state root from genesis block"""
state_data = {
"chain_id": genesis_block.chain_id,
"chain_type": genesis_block.chain_type.value,
"purpose": genesis_block.purpose,
"name": genesis_block.name,
"timestamp": genesis_block.timestamp.isoformat(),
"accounts": [account.dict() for account in genesis_block.accounts],
"contracts": [contract.dict() for contract in genesis_block.contracts],
"parameters": genesis_block.parameters.dict()
}
state_json = json.dumps(state_data, sort_keys=True)
return hashlib.sha256(state_json.encode()).hexdigest()
def _merge_configs(self, template: Dict[str, Any], custom: Dict[str, Any]) -> Dict[str, Any]:
"""Merge template configuration with custom overrides"""
result = template.copy()
if 'genesis' in custom:
for key, value in custom['genesis'].items():
if isinstance(value, dict) and key in result.get('genesis', {}):
result['genesis'][key].update(value)
else:
if 'genesis' not in result:
result['genesis'] = {}
result['genesis'][key] = value
return result
class ValidationResult:
"""Genesis validation result"""
def __init__(self, is_valid: bool, errors: list, checks: dict):
self.is_valid = is_valid
self.errors = errors
self.checks = checks

668
cli/core/marketplace.py Executable file
View File

@@ -0,0 +1,668 @@
"""
Global chain marketplace system
"""
import asyncio
import json
import hashlib
import time
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Any, Set
from dataclasses import dataclass, asdict
from enum import Enum
import uuid
from decimal import Decimal
from collections import defaultdict
from core.config import MultiChainConfig
from core.node_client import NodeClient
class ChainType(Enum):
"""Chain types in marketplace"""
TOPIC = "topic"
PRIVATE = "private"
RESEARCH = "research"
ENTERPRISE = "enterprise"
GOVERNANCE = "governance"
class MarketplaceStatus(Enum):
"""Marketplace listing status"""
ACTIVE = "active"
PENDING = "pending"
SOLD = "sold"
EXPIRED = "expired"
DELISTED = "delisted"
class TransactionStatus(Enum):
"""Transaction status"""
PENDING = "pending"
CONFIRMED = "confirmed"
COMPLETED = "completed"
FAILED = "failed"
REFUNDED = "refunded"
@dataclass
class ChainListing:
"""Chain marketplace listing"""
listing_id: str
chain_id: str
chain_name: str
chain_type: ChainType
description: str
seller_id: str
price: Decimal
currency: str
status: MarketplaceStatus
created_at: datetime
expires_at: datetime
metadata: Dict[str, Any]
chain_specifications: Dict[str, Any]
performance_metrics: Dict[str, Any]
reputation_requirements: Dict[str, Any]
governance_rules: Dict[str, Any]
@dataclass
class MarketplaceTransaction:
"""Marketplace transaction"""
transaction_id: str
listing_id: str
buyer_id: str
seller_id: str
chain_id: str
price: Decimal
currency: str
status: TransactionStatus
created_at: datetime
completed_at: Optional[datetime]
escrow_address: str
smart_contract_address: str
transaction_hash: Optional[str]
metadata: Dict[str, Any]
@dataclass
class ChainEconomy:
"""Chain economic metrics"""
chain_id: str
total_value_locked: Decimal
daily_volume: Decimal
market_cap: Decimal
price_history: List[Dict[str, Any]]
transaction_count: int
active_users: int
agent_count: int
governance_tokens: Decimal
staking_rewards: Decimal
last_updated: datetime
@dataclass
class MarketplaceMetrics:
"""Marketplace performance metrics"""
total_listings: int
active_listings: int
total_transactions: int
total_volume: Decimal
average_price: Decimal
popular_chain_types: Dict[str, int]
top_sellers: List[Dict[str, Any]]
price_trends: Dict[str, List[Decimal]]
market_sentiment: float
last_updated: datetime
class GlobalChainMarketplace:
"""Global chain marketplace system"""
def __init__(self, config: MultiChainConfig):
self.config = config
self.listings: Dict[str, ChainListing] = {}
self.transactions: Dict[str, MarketplaceTransaction] = {}
self.chain_economies: Dict[str, ChainEconomy] = {}
self.user_reputations: Dict[str, float] = {}
self.market_metrics: Optional[MarketplaceMetrics] = None
self.escrow_contracts: Dict[str, Dict[str, Any]] = {}
self.price_history: Dict[str, List[Decimal]] = defaultdict(list)
# Marketplace thresholds
self.thresholds = {
'min_reputation_score': 0.5,
'max_listing_duration_days': 30,
'escrow_fee_percentage': 0.02, # 2%
'marketplace_fee_percentage': 0.01, # 1%
'min_chain_price': Decimal('0.001'),
'max_chain_price': Decimal('1000000')
}
async def create_listing(self, chain_id: str, chain_name: str, chain_type: ChainType,
description: str, seller_id: str, price: Decimal, currency: str,
chain_specifications: Dict[str, Any], metadata: Dict[str, Any]) -> Optional[str]:
"""Create a new chain listing in the marketplace"""
try:
# Validate seller reputation
if self.user_reputations.get(seller_id, 0) < self.thresholds['min_reputation_score']:
return None
# Validate price
if price < self.thresholds['min_chain_price'] or price > self.thresholds['max_chain_price']:
return None
# Check if chain already has active listing
for listing in self.listings.values():
if listing.chain_id == chain_id and listing.status == MarketplaceStatus.ACTIVE:
return None
# Create listing
listing_id = str(uuid.uuid4())
expires_at = datetime.now() + timedelta(days=self.thresholds['max_listing_duration_days'])
listing = ChainListing(
listing_id=listing_id,
chain_id=chain_id,
chain_name=chain_name,
chain_type=chain_type,
description=description,
seller_id=seller_id,
price=price,
currency=currency,
status=MarketplaceStatus.ACTIVE,
created_at=datetime.now(),
expires_at=expires_at,
metadata=metadata,
chain_specifications=chain_specifications,
performance_metrics={},
reputation_requirements={"min_score": 0.5},
governance_rules={"voting_threshold": 0.6}
)
self.listings[listing_id] = listing
# Update price history
self.price_history[chain_id].append(price)
# Update market metrics
await self._update_market_metrics()
return listing_id
except Exception as e:
print(f"Error creating listing: {e}")
return None
async def purchase_chain(self, listing_id: str, buyer_id: str, payment_method: str) -> Optional[str]:
"""Purchase a chain from the marketplace"""
try:
listing = self.listings.get(listing_id)
if not listing or listing.status != MarketplaceStatus.ACTIVE:
return None
# Validate buyer reputation
if self.user_reputations.get(buyer_id, 0) < self.thresholds['min_reputation_score']:
return None
# Check if listing is expired
if datetime.now() > listing.expires_at:
listing.status = MarketplaceStatus.EXPIRED
return None
# Create transaction
transaction_id = str(uuid.uuid4())
escrow_address = f"escrow_{transaction_id[:8]}"
smart_contract_address = f"contract_{transaction_id[:8]}"
transaction = MarketplaceTransaction(
transaction_id=transaction_id,
listing_id=listing_id,
buyer_id=buyer_id,
seller_id=listing.seller_id,
chain_id=listing.chain_id,
price=listing.price,
currency=listing.currency,
status=TransactionStatus.PENDING,
created_at=datetime.now(),
completed_at=None,
escrow_address=escrow_address,
smart_contract_address=smart_contract_address,
transaction_hash=None,
metadata={"payment_method": payment_method}
)
self.transactions[transaction_id] = transaction
# Create escrow contract
await self._create_escrow_contract(transaction)
# Update listing status
listing.status = MarketplaceStatus.SOLD
# Update market metrics
await self._update_market_metrics()
return transaction_id
except Exception as e:
print(f"Error purchasing chain: {e}")
return None
async def complete_transaction(self, transaction_id: str, transaction_hash: str) -> bool:
"""Complete a marketplace transaction"""
try:
transaction = self.transactions.get(transaction_id)
if not transaction or transaction.status != TransactionStatus.PENDING:
return False
# Update transaction
transaction.status = TransactionStatus.COMPLETED
transaction.completed_at = datetime.now()
transaction.transaction_hash = transaction_hash
# Release escrow
await self._release_escrow(transaction)
# Update reputations
self._update_user_reputation(transaction.buyer_id, 0.1) # Positive update
self._update_user_reputation(transaction.seller_id, 0.1)
# Update chain economy
await self._update_chain_economy(transaction.chain_id, transaction.price)
# Update market metrics
await self._update_market_metrics()
return True
except Exception as e:
print(f"Error completing transaction: {e}")
return False
async def get_chain_economy(self, chain_id: str) -> Optional[ChainEconomy]:
"""Get economic metrics for a specific chain"""
try:
if chain_id not in self.chain_economies:
# Initialize chain economy
self.chain_economies[chain_id] = ChainEconomy(
chain_id=chain_id,
total_value_locked=Decimal('0'),
daily_volume=Decimal('0'),
market_cap=Decimal('0'),
price_history=[],
transaction_count=0,
active_users=0,
agent_count=0,
governance_tokens=Decimal('0'),
staking_rewards=Decimal('0'),
last_updated=datetime.now()
)
# Update with latest data
await self._update_chain_economy(chain_id)
return self.chain_economies[chain_id]
except Exception as e:
print(f"Error getting chain economy: {e}")
return None
async def search_listings(self, chain_type: Optional[ChainType] = None,
min_price: Optional[Decimal] = None,
max_price: Optional[Decimal] = None,
seller_id: Optional[str] = None,
status: Optional[MarketplaceStatus] = None) -> List[ChainListing]:
"""Search chain listings with filters"""
try:
results = []
for listing in self.listings.values():
# Apply filters
if chain_type and listing.chain_type != chain_type:
continue
if min_price and listing.price < min_price:
continue
if max_price and listing.price > max_price:
continue
if seller_id and listing.seller_id != seller_id:
continue
if status and listing.status != status:
continue
results.append(listing)
# Sort by creation date (newest first)
results.sort(key=lambda x: x.created_at, reverse=True)
return results
except Exception as e:
print(f"Error searching listings: {e}")
return []
async def get_user_transactions(self, user_id: str, role: str = "both") -> List[MarketplaceTransaction]:
"""Get transactions for a specific user"""
try:
results = []
for transaction in self.transactions.values():
if role == "buyer" and transaction.buyer_id != user_id:
continue
if role == "seller" and transaction.seller_id != user_id:
continue
if role == "both" and transaction.buyer_id != user_id and transaction.seller_id != user_id:
continue
results.append(transaction)
# Sort by creation date (newest first)
results.sort(key=lambda x: x.created_at, reverse=True)
return results
except Exception as e:
print(f"Error getting user transactions: {e}")
return []
async def get_marketplace_overview(self) -> Dict[str, Any]:
"""Get comprehensive marketplace overview"""
try:
await self._update_market_metrics()
if not self.market_metrics:
return {}
# Calculate additional metrics
total_volume_24h = await self._calculate_24h_volume()
top_chains = await self._get_top_performing_chains()
price_trends = await self._calculate_price_trends()
overview = {
"marketplace_metrics": asdict(self.market_metrics),
"volume_24h": total_volume_24h,
"top_performing_chains": top_chains,
"price_trends": price_trends,
"chain_types_distribution": await self._get_chain_types_distribution(),
"user_activity": await self._get_user_activity_metrics(),
"escrow_summary": await self._get_escrow_summary()
}
return overview
except Exception as e:
print(f"Error getting marketplace overview: {e}")
return {}
async def _create_escrow_contract(self, transaction: MarketplaceTransaction):
"""Create escrow contract for transaction"""
try:
escrow_contract = {
"contract_address": transaction.escrow_address,
"transaction_id": transaction.transaction_id,
"amount": transaction.price,
"currency": transaction.currency,
"buyer_id": transaction.buyer_id,
"seller_id": transaction.seller_id,
"created_at": datetime.now(),
"status": "active",
"release_conditions": {
"transaction_confirmed": False,
"dispute_resolved": False
}
}
self.escrow_contracts[transaction.escrow_address] = escrow_contract
except Exception as e:
print(f"Error creating escrow contract: {e}")
async def _release_escrow(self, transaction: MarketplaceTransaction):
"""Release escrow funds"""
try:
escrow_contract = self.escrow_contracts.get(transaction.escrow_address)
if escrow_contract:
escrow_contract["status"] = "released"
escrow_contract["released_at"] = datetime.now()
escrow_contract["release_conditions"]["transaction_confirmed"] = True
# Calculate fees
escrow_fee = transaction.price * Decimal(str(self.thresholds['escrow_fee_percentage']))
marketplace_fee = transaction.price * Decimal(str(self.thresholds['marketplace_fee_percentage']))
seller_amount = transaction.price - escrow_fee - marketplace_fee
escrow_contract["fee_breakdown"] = {
"escrow_fee": escrow_fee,
"marketplace_fee": marketplace_fee,
"seller_amount": seller_amount
}
except Exception as e:
print(f"Error releasing escrow: {e}")
async def _update_chain_economy(self, chain_id: str, transaction_price: Optional[Decimal] = None):
"""Update chain economic metrics"""
try:
if chain_id not in self.chain_economies:
self.chain_economies[chain_id] = ChainEconomy(
chain_id=chain_id,
total_value_locked=Decimal('0'),
daily_volume=Decimal('0'),
market_cap=Decimal('0'),
price_history=[],
transaction_count=0,
active_users=0,
agent_count=0,
governance_tokens=Decimal('0'),
staking_rewards=Decimal('0'),
last_updated=datetime.now()
)
economy = self.chain_economies[chain_id]
# Update with transaction price if provided
if transaction_price:
economy.daily_volume += transaction_price
economy.transaction_count += 1
# Add to price history
economy.price_history.append({
"price": float(transaction_price),
"timestamp": datetime.now().isoformat(),
"volume": float(transaction_price)
})
# Update other metrics (would be fetched from chain nodes)
# For now, using mock data
economy.active_users = max(10, economy.active_users)
economy.agent_count = max(5, economy.agent_count)
economy.total_value_locked = economy.daily_volume * Decimal('10') # Mock TVL
economy.market_cap = economy.daily_volume * Decimal('100') # Mock market cap
economy.last_updated = datetime.now()
except Exception as e:
print(f"Error updating chain economy: {e}")
async def _update_market_metrics(self):
"""Update marketplace performance metrics"""
try:
total_listings = len(self.listings)
active_listings = len([l for l in self.listings.values() if l.status == MarketplaceStatus.ACTIVE])
total_transactions = len(self.transactions)
# Calculate total volume and average price
completed_transactions = [t for t in self.transactions.values() if t.status == TransactionStatus.COMPLETED]
total_volume = sum(t.price for t in completed_transactions)
average_price = total_volume / len(completed_transactions) if completed_transactions else Decimal('0')
# Popular chain types
chain_types = defaultdict(int)
for listing in self.listings.values():
chain_types[listing.chain_type.value] += 1
# Top sellers
seller_stats = defaultdict(lambda: {"count": 0, "volume": Decimal('0')})
for transaction in completed_transactions:
seller_stats[transaction.seller_id]["count"] += 1
seller_stats[transaction.seller_id]["volume"] += transaction.price
top_sellers = [
{"seller_id": seller_id, "sales_count": stats["count"], "total_volume": float(stats["volume"])}
for seller_id, stats in seller_stats.items()
]
top_sellers.sort(key=lambda x: x["total_volume"], reverse=True)
top_sellers = top_sellers[:10] # Top 10
# Price trends
price_trends = {}
for chain_id, prices in self.price_history.items():
if len(prices) >= 2:
trend = (prices[-1] - prices[-2]) / prices[-2] if prices[-2] != 0 else 0
price_trends[chain_id] = [trend]
# Market sentiment (mock calculation)
market_sentiment = 0.5 # Neutral
if completed_transactions:
positive_ratio = len(completed_transactions) / max(1, total_transactions)
market_sentiment = min(1.0, positive_ratio * 1.2)
self.market_metrics = MarketplaceMetrics(
total_listings=total_listings,
active_listings=active_listings,
total_transactions=total_transactions,
total_volume=total_volume,
average_price=average_price,
popular_chain_types=dict(chain_types),
top_sellers=top_sellers,
price_trends=price_trends,
market_sentiment=market_sentiment,
last_updated=datetime.now()
)
except Exception as e:
print(f"Error updating market metrics: {e}")
def _update_user_reputation(self, user_id: str, delta: float):
"""Update user reputation"""
try:
current_rep = self.user_reputations.get(user_id, 0.5)
new_rep = max(0.0, min(1.0, current_rep + delta))
self.user_reputations[user_id] = new_rep
except Exception as e:
print(f"Error updating user reputation: {e}")
async def _calculate_24h_volume(self) -> Decimal:
"""Calculate 24-hour trading volume"""
try:
cutoff_time = datetime.now() - timedelta(hours=24)
recent_transactions = [
t for t in self.transactions.values()
if t.created_at >= cutoff_time and t.status == TransactionStatus.COMPLETED
]
return sum(t.price for t in recent_transactions)
except Exception as e:
print(f"Error calculating 24h volume: {e}")
return Decimal('0')
async def _get_top_performing_chains(self, limit: int = 10) -> List[Dict[str, Any]]:
"""Get top performing chains by volume"""
try:
chain_performance = defaultdict(lambda: {"volume": Decimal('0'), "transactions": 0})
for transaction in self.transactions.values():
if transaction.status == TransactionStatus.COMPLETED:
chain_performance[transaction.chain_id]["volume"] += transaction.price
chain_performance[transaction.chain_id]["transactions"] += 1
top_chains = [
{
"chain_id": chain_id,
"volume": float(stats["volume"]),
"transactions": stats["transactions"]
}
for chain_id, stats in chain_performance.items()
]
top_chains.sort(key=lambda x: x["volume"], reverse=True)
return top_chains[:limit]
except Exception as e:
print(f"Error getting top performing chains: {e}")
return []
async def _calculate_price_trends(self) -> Dict[str, List[float]]:
"""Calculate price trends for all chains"""
try:
trends = {}
for chain_id, prices in self.price_history.items():
if len(prices) >= 2:
# Calculate simple trend
recent_prices = list(prices)[-10:] # Last 10 prices
if len(recent_prices) >= 2:
trend = (recent_prices[-1] - recent_prices[0]) / recent_prices[0] if recent_prices[0] != 0 else 0
trends[chain_id] = [float(trend)]
return trends
except Exception as e:
print(f"Error calculating price trends: {e}")
return {}
async def _get_chain_types_distribution(self) -> Dict[str, int]:
"""Get distribution of chain types"""
try:
distribution = defaultdict(int)
for listing in self.listings.values():
distribution[listing.chain_type.value] += 1
return dict(distribution)
except Exception as e:
print(f"Error getting chain types distribution: {e}")
return {}
async def _get_user_activity_metrics(self) -> Dict[str, Any]:
"""Get user activity metrics"""
try:
active_buyers = set()
active_sellers = set()
for transaction in self.transactions.values():
if transaction.created_at >= datetime.now() - timedelta(days=7):
active_buyers.add(transaction.buyer_id)
active_sellers.add(transaction.seller_id)
return {
"active_buyers_7d": len(active_buyers),
"active_sellers_7d": len(active_sellers),
"total_unique_users": len(set(self.user_reputations.keys())),
"average_reputation": sum(self.user_reputations.values()) / len(self.user_reputations) if self.user_reputations else 0
}
except Exception as e:
print(f"Error getting user activity metrics: {e}")
return {}
async def _get_escrow_summary(self) -> Dict[str, Any]:
"""Get escrow contract summary"""
try:
active_escrows = len([e for e in self.escrow_contracts.values() if e["status"] == "active"])
released_escrows = len([e for e in self.escrow_contracts.values() if e["status"] == "released"])
total_escrow_value = sum(
Decimal(str(e["amount"])) for e in self.escrow_contracts.values()
if e["status"] == "active"
)
return {
"active_escrows": active_escrows,
"released_escrows": released_escrows,
"total_escrow_value": float(total_escrow_value),
"escrow_fee_collected": float(total_escrow_value * Decimal(str(self.thresholds['escrow_fee_percentage'])))
}
except Exception as e:
print(f"Error getting escrow summary: {e}")
return {}

374
cli/core/node_client.py Executable file
View File

@@ -0,0 +1,374 @@
"""
Node client for multi-chain operations
"""
import asyncio
import httpx
import json
from typing import Dict, List, Optional, Any
from core.config import NodeConfig
from models.chain import ChainInfo, ChainType, ChainStatus, ConsensusAlgorithm
class NodeClient:
"""Client for communicating with AITBC nodes"""
def __init__(self, node_config: NodeConfig):
self.config = node_config
self._client: Optional[httpx.AsyncClient] = None
self._session_id: Optional[str] = None
async def __aenter__(self):
"""Async context manager entry"""
self._client = httpx.AsyncClient(
timeout=httpx.Timeout(self.config.timeout),
limits=httpx.Limits(max_connections=self.config.max_connections)
)
await self._authenticate()
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
"""Async context manager exit"""
if self._client:
await self._client.aclose()
async def _authenticate(self):
"""Authenticate with the node"""
try:
# For now, we'll use a simple authentication
# In production, this would use proper authentication
response = await self._client.post(
f"{self.config.endpoint}/api/auth",
json={"action": "authenticate"}
)
if response.status_code == 200:
data = response.json()
self._session_id = data.get("session_id")
except Exception as e:
# For development, we'll continue without authentication
pass # print(f"Warning: Could not authenticate with node {self.config.id}: {e}")
async def get_node_info(self) -> Dict[str, Any]:
"""Get node information"""
try:
response = await self._client.get(f"{self.config.endpoint}/api/node/info")
if response.status_code == 200:
return response.json()
else:
raise Exception(f"Node info request failed: {response.status_code}")
except Exception as e:
# Return mock data for development
return self._get_mock_node_info()
async def get_hosted_chains(self) -> List[ChainInfo]:
"""Get all chains hosted by this node"""
try:
health_url = f"{self.config.endpoint}/health"
if "/rpc" in self.config.endpoint:
health_url = self.config.endpoint.replace("/rpc", "/health")
response = await self._client.get(health_url)
if response.status_code == 200:
health_data = response.json()
chains = health_data.get("supported_chains", ["ait-devnet"])
result = []
for cid in chains:
# Try to fetch real block height
block_height = 0
try:
head_url = f"{self.config.endpoint}/rpc/head?chain_id={cid}"
if "/rpc" in self.config.endpoint:
head_url = f"{self.config.endpoint}/head?chain_id={cid}"
head_resp = await self._client.get(head_url, timeout=2.0)
if head_resp.status_code == 200:
head_data = head_resp.json()
block_height = head_data.get("height", 0)
except Exception:
pass
result.append(self._parse_chain_info({
"id": cid,
"name": f"AITBC {cid.split('-')[-1].capitalize()} Chain",
"type": "topic" if "health" in cid else "main",
"purpose": "specialized" if "health" in cid else "general",
"status": "active",
"size_mb": 50.5,
"nodes": 3,
"smart_contracts": 5,
"active_clients": 25,
"active_miners": 8,
"block_height": block_height,
"privacy": {"visibility": "public"}
}))
return result
else:
return self._get_mock_chains()
except Exception as e:
return self._get_mock_chains()
async def get_chain_info(self, chain_id: str) -> Optional[ChainInfo]:
"""Get specific chain information"""
try:
# Re-use the health endpoint logic
health_url = f"{self.config.endpoint}/health"
if "/rpc" in self.config.endpoint:
health_url = self.config.endpoint.replace("/rpc", "/health")
response = await self._client.get(health_url)
if response.status_code == 200:
health_data = response.json()
chains = health_data.get("supported_chains", ["ait-devnet"])
if chain_id in chains:
block_height = 0
try:
head_url = f"{self.config.endpoint}/rpc/head?chain_id={chain_id}"
if "/rpc" in self.config.endpoint:
head_url = f"{self.config.endpoint}/head?chain_id={chain_id}"
head_resp = await self._client.get(head_url, timeout=2.0)
if head_resp.status_code == 200:
head_data = head_resp.json()
block_height = head_data.get("height", 0)
except Exception:
pass
return self._parse_chain_info({
"id": chain_id,
"name": f"AITBC {chain_id.split('-')[-1].capitalize()} Chain",
"type": "topic" if "health" in chain_id else "main",
"purpose": "specialized" if "health" in chain_id else "general",
"status": "active",
"size_mb": 50.5,
"nodes": 3,
"smart_contracts": 5,
"active_clients": 25,
"active_miners": 8,
"block_height": block_height,
"privacy": {"visibility": "public"}
})
return None
except Exception as e:
# Fallback to pure mock
chains = self._get_mock_chains()
for chain in chains:
if chain.id == chain_id:
return chain
return None
async def create_chain(self, genesis_block: Dict[str, Any]) -> str:
"""Create a new chain on this node"""
try:
response = await self._client.post(
f"{self.config.endpoint}/api/chains",
json=genesis_block
)
if response.status_code == 201:
data = response.json()
return data["chain_id"]
else:
raise Exception(f"Chain creation failed: {response.status_code}")
except Exception as e:
# Mock chain creation for development
chain_id = genesis_block.get("chain_id", f"MOCK-CHAIN-{hash(str(genesis_block)) % 10000}")
print(f"Mock created chain {chain_id} on node {self.config.id}")
return chain_id
async def delete_chain(self, chain_id: str) -> bool:
"""Delete a chain from this node"""
try:
response = await self._client.delete(f"{self.config.endpoint}/api/chains/{chain_id}")
if response.status_code == 200:
return True
else:
raise Exception(f"Chain deletion failed: {response.status_code}")
except Exception as e:
# Mock chain deletion for development
print(f"Mock deleted chain {chain_id} from node {self.config.id}")
return True
async def get_chain_stats(self, chain_id: str) -> Dict[str, Any]:
"""Get chain statistics"""
try:
response = await self._client.get(f"{self.config.endpoint}/api/chains/{chain_id}/stats")
if response.status_code == 200:
return response.json()
else:
raise Exception(f"Chain stats request failed: {response.status_code}")
except Exception as e:
# Return mock stats for development
return self._get_mock_chain_stats(chain_id)
async def backup_chain(self, chain_id: str, backup_path: str) -> Dict[str, Any]:
"""Backup a chain"""
try:
response = await self._client.post(
f"{self.config.endpoint}/api/chains/{chain_id}/backup",
json={"backup_path": backup_path}
)
if response.status_code == 200:
return response.json()
else:
raise Exception(f"Chain backup failed: {response.status_code}")
except Exception as e:
# Mock backup for development
backup_info = {
"chain_id": chain_id,
"backup_file": f"{backup_path}/{chain_id}_backup.tar.gz",
"original_size_mb": 100.0,
"backup_size_mb": 50.0,
"checksum": "mock_checksum_12345"
}
print(f"Mock backed up chain {chain_id} to {backup_info['backup_file']}")
return backup_info
async def restore_chain(self, backup_file: str, chain_id: Optional[str] = None) -> Dict[str, Any]:
"""Restore a chain from backup"""
try:
response = await self._client.post(
f"{self.config.endpoint}/api/chains/restore",
json={"backup_file": backup_file, "chain_id": chain_id}
)
if response.status_code == 200:
return response.json()
else:
raise Exception(f"Chain restore failed: {response.status_code}")
except Exception as e:
# Mock restore for development
restore_info = {
"chain_id": chain_id or "RESTORED-MOCK-CHAIN",
"blocks_restored": 1000,
"verification_passed": True
}
print(f"Mock restored chain from {backup_file}")
return restore_info
def _parse_chain_info(self, chain_data: Dict[str, Any]) -> ChainInfo:
"""Parse chain data from node response"""
from datetime import datetime
from models.chain import PrivacyConfig
return ChainInfo(
id=chain_data.get("chain_id", chain_data.get("id", "unknown")),
type=ChainType(chain_data.get("chain_type", "topic")),
purpose=chain_data.get("purpose", "unknown"),
name=chain_data.get("name", "Unnamed Chain"),
description=chain_data.get("description"),
status=ChainStatus(chain_data.get("status", "active")),
created_at=datetime.fromisoformat(chain_data.get("created_at", "2024-01-01T00:00:00")),
block_height=chain_data.get("block_height", 0),
size_mb=chain_data.get("size_mb", 0.0),
node_count=chain_data.get("node_count", 1),
active_nodes=chain_data.get("active_nodes", 1),
contract_count=chain_data.get("contract_count", 0),
client_count=chain_data.get("client_count", 0),
miner_count=chain_data.get("miner_count", 0),
agent_count=chain_data.get("agent_count", 0),
consensus_algorithm=ConsensusAlgorithm(chain_data.get("consensus_algorithm", "pos")),
block_time=chain_data.get("block_time", 5),
tps=chain_data.get("tps", 0.0),
avg_block_time=chain_data.get("avg_block_time", 5.0),
avg_gas_used=chain_data.get("avg_gas_used", 0),
growth_rate_mb_per_day=chain_data.get("growth_rate_mb_per_day", 0.0),
gas_price=chain_data.get("gas_price", 20000000000),
memory_usage_mb=chain_data.get("memory_usage_mb", 0.0),
disk_usage_mb=chain_data.get("disk_usage_mb", 0.0),
privacy=PrivacyConfig(
visibility=chain_data.get("privacy", {}).get("visibility", "public"),
access_control=chain_data.get("privacy", {}).get("access_control", "open")
)
)
def _get_mock_node_info(self) -> Dict[str, Any]:
"""Get mock node information for development"""
return {
"node_id": self.config.id,
"type": "full",
"status": "active",
"version": "1.0.0",
"uptime_days": 30,
"uptime_hours": 720,
"hosted_chains": {},
"cpu_usage": 25.5,
"memory_usage_mb": 1024.0,
"disk_usage_mb": 10240.0,
"network_in_mb": 10.5,
"network_out_mb": 8.2
}
def _get_mock_chains(self) -> List[ChainInfo]:
"""Get mock chains for development"""
from datetime import datetime
from models.chain import PrivacyConfig
return [
ChainInfo(
id="AITBC-TOPIC-HEALTHCARE-001",
type=ChainType.TOPIC,
purpose="healthcare",
name="Healthcare AI Chain",
description="A specialized chain for healthcare AI applications",
status=ChainStatus.ACTIVE,
created_at=datetime.now(),
block_height=1000,
size_mb=50.5,
node_count=3,
active_nodes=3,
contract_count=5,
client_count=25,
miner_count=8,
agent_count=12,
consensus_algorithm=ConsensusAlgorithm.POS,
block_time=3,
tps=15.5,
avg_block_time=3.2,
avg_gas_used=5000000,
growth_rate_mb_per_day=2.1,
gas_price=20000000000,
memory_usage_mb=256.0,
disk_usage_mb=512.0,
privacy=PrivacyConfig(visibility="public", access_control="open")
),
ChainInfo(
id="AITBC-PRIVATE-COLLAB-001",
type=ChainType.PRIVATE,
purpose="collaboration",
name="Private Research Chain",
description="A private chain for trusted agent collaboration",
status=ChainStatus.ACTIVE,
created_at=datetime.now(),
block_height=500,
size_mb=25.2,
node_count=2,
active_nodes=2,
contract_count=3,
client_count=8,
miner_count=4,
agent_count=6,
consensus_algorithm=ConsensusAlgorithm.POA,
block_time=5,
tps=8.0,
avg_block_time=5.1,
avg_gas_used=3000000,
growth_rate_mb_per_day=1.0,
gas_price=15000000000,
memory_usage_mb=128.0,
disk_usage_mb=256.0,
privacy=PrivacyConfig(visibility="private", access_control="invite_only")
)
]
def _get_mock_chain_stats(self, chain_id: str) -> Dict[str, Any]:
"""Get mock chain statistics for development"""
return {
"chain_id": chain_id,
"block_height": 1000,
"tps": 15.5,
"avg_block_time": 3.2,
"gas_price": 20000000000,
"memory_usage_mb": 256.0,
"disk_usage_mb": 512.0,
"active_nodes": 3,
"client_count": 25,
"miner_count": 8,
"agent_count": 12,
"last_block_time": "2024-03-02T10:00:00Z"
}