fix: CLI bugs - network KeyError, mine-status/market-list missing handlers
All checks were successful
CLI Tests / test-cli (push) Successful in 1m14s
Security Scanning / security-scan (push) Successful in 1m23s

- Fix network command: use .get() with defaults for chain_id, rpc_version
  (RPC returns height/hash/timestamp/tx_count, not chain_id/rpc_version)
- Add missing dispatch handlers for mine-start, mine-stop, mine-status
- Add missing dispatch handlers for market-list, market-create, ai-submit
- Enhanced dev_heartbeat.py with AITBC blockchain health checks
  (monitors local RPC, genesis RPC, height diff, service status)
This commit is contained in:
2026-03-30 14:40:56 +02:00
parent 0e551f3bbb
commit 5b62791e95
2 changed files with 172 additions and 16 deletions

View File

@@ -1340,11 +1340,11 @@ def main():
network_info = get_network_status(rpc_url=args.rpc_url) network_info = get_network_status(rpc_url=args.rpc_url)
if network_info: if network_info:
print("Network Status:") print("Network Status:")
print(f" Height: {network_info['height']}") print(f" Height: {network_info.get('height', 'N/A')}")
print(f" Latest Block: {network_info['hash'][:16]}...") print(f" Latest Block: {str(network_info.get('hash', 'N/A'))[:16]}...")
print(f" Chain ID: {network_info['chain_id']}") print(f" Chain ID: {network_info.get('chain_id', 'ait-mainnet')}")
print(f" RPC Version: {network_info['rpc_version']}") print(f" Tx Count: {network_info.get('tx_count', 0)}")
print(f" Timestamp: {network_info['timestamp']}") print(f" Timestamp: {network_info.get('timestamp', 'N/A')}")
else: else:
sys.exit(1) sys.exit(1)
@@ -1507,6 +1507,74 @@ def main():
else: else:
sys.exit(1) 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)
else: else:
parser.print_help() parser.print_help()

View File

@@ -13,6 +13,11 @@ from pathlib import Path
REPO_ROOT = Path("/opt/aitbc") REPO_ROOT = Path("/opt/aitbc")
LOGS_DIR = REPO_ROOT / "logs" LOGS_DIR = REPO_ROOT / "logs"
# AITBC blockchain config
LOCAL_RPC = "http://localhost:8006"
GENESIS_RPC = "http://10.1.223.93:8006"
MAX_HEIGHT_DIFF = 10 # acceptable block height difference between nodes
def sh(cmd, cwd=REPO_ROOT): def sh(cmd, cwd=REPO_ROOT):
"""Run shell command, return (returncode, stdout).""" """Run shell command, return (returncode, stdout)."""
result = subprocess.run(cmd, shell=True, cwd=cwd, capture_output=True, text=True) result = subprocess.run(cmd, shell=True, cwd=cwd, capture_output=True, text=True)
@@ -86,18 +91,32 @@ def check_vulnerabilities():
"""Run security audits for Python and Node dependencies.""" """Run security audits for Python and Node dependencies."""
issues = [] issues = []
# Python: pip-audit (if available) # Python: pip-audit (if available)
rc, out = sh("pip-audit --requirement <(poetry export --without-hashes) 2>&1") # Export requirements to temp file first to avoid shell process substitution issues
if rc == 0: rc_export, req_content = sh("poetry export --without-hashes")
# No vulnerabilities if rc_export == 0 and req_content:
pass import tempfile
import os
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
f.write(req_content)
temp_req_file = f.name
try:
rc, out = sh(f"pip-audit --requirement {temp_req_file} 2>&1")
if rc == 0:
# No vulnerabilities
pass
else:
# pip-audit returns non-zero when vulns found; parse output for count
# Usually output contains lines with "Found X vulnerabilities"
if "vulnerabilities" in out.lower():
issues.append(f"Python dependencies: vulnerabilities detected\n```\n{out[:2000]}\n```")
else:
# Command failed for another reason (maybe not installed)
pass
finally:
os.unlink(temp_req_file)
else: else:
# pip-audit returns non-zero when vulns found; parse output for count # Failed to export requirements
# Usually output contains lines with "Found X vulnerabilities" pass
if "vulnerabilities" in out.lower():
issues.append(f"Python dependencies: vulnerabilities detected\n```\n{out[:2000]}\n```")
else:
# Command failed for another reason (maybe not installed)
pass
# Node: npm audit (if package.json exists) # Node: npm audit (if package.json exists)
if (REPO_ROOT / "package.json").exists(): if (REPO_ROOT / "package.json").exists():
rc, out = sh("npm audit --json") rc, out = sh("npm audit --json")
@@ -111,10 +130,79 @@ def check_vulnerabilities():
issues.append("Node dependencies: npm audit failed to parse") issues.append("Node dependencies: npm audit failed to parse")
return issues return issues
def check_blockchain_health():
"""Check AITBC blockchain node health on this follower node."""
result = {"local_ok": False, "local_height": None, "genesis_ok": False,
"genesis_height": None, "sync_diff": None, "services": {},
"issues": []}
# Local RPC health
try:
import urllib.request
with urllib.request.urlopen(f"{LOCAL_RPC}/rpc/head", timeout=5) as resp:
data = json.loads(resp.read())
result["local_ok"] = True
result["local_height"] = data.get("height")
except Exception as e:
result["issues"].append(f"Local RPC ({LOCAL_RPC}) unreachable: {e}")
# Genesis node RPC
try:
import urllib.request
with urllib.request.urlopen(f"{GENESIS_RPC}/rpc/head", timeout=5) as resp:
data = json.loads(resp.read())
result["genesis_ok"] = True
result["genesis_height"] = data.get("height")
except Exception:
result["issues"].append(f"Genesis RPC ({GENESIS_RPC}) unreachable")
# Sync diff
if result["local_height"] is not None and result["genesis_height"] is not None:
result["sync_diff"] = result["local_height"] - result["genesis_height"]
# Service status
for svc in ["aitbc-blockchain-node", "aitbc-blockchain-rpc"]:
rc, out = sh(f"systemctl is-active {svc}.service")
result["services"][svc] = out.strip() if rc == 0 else "unknown"
return result
def main(): def main():
report = [] report = []
issues = 0 issues = 0
# AITBC Blockchain (always reported)
bc = check_blockchain_health()
bc_lines = []
bc_issue = False
if bc["local_ok"]:
bc_lines.append(f"- **Follower height**: {bc['local_height']}")
else:
bc_lines.append("- **Follower RPC**: DOWN")
bc_issue = True
if bc["genesis_ok"]:
bc_lines.append(f"- **Genesis height**: {bc['genesis_height']}")
else:
bc_lines.append("- **Genesis RPC**: unreachable")
bc_issue = True
if bc["sync_diff"] is not None:
bc_lines.append(f"- **Height diff**: {bc['sync_diff']:+d} (follower {'ahead' if bc['sync_diff'] > 0 else 'behind'})")
for svc, status in bc["services"].items():
bc_lines.append(f"- **{svc}**: {status}")
if status != "active":
bc_issue = True
for iss in bc["issues"]:
bc_lines.append(f"- {iss}")
bc_issue = True
if bc_issue:
issues += 1
report.append("### Blockchain: issues detected\n")
else:
report.append("### Blockchain: healthy\n")
report.extend(bc_lines)
report.append("")
# Git # Git
git = check_git_status() git = check_git_status()
if git and git["changed"] > 0: if git and git["changed"] > 0: