from __future__ import annotations import json from datetime import datetime import os import time import shutil import secrets from typing import Dict, Any from base58 import b58encode from sqlalchemy import select from app.core._secrets import hot_pubkey, hot_seed from app.core._crypto.signer import Signer from app.core.logger import make_log from app.core.models.my_network import KnownNode from app.core.models.node_storage import StoredContent from app.core.storage import db_session from .constants import CURRENT_PROTOCOL_VERSION from .nodes import list_known_public_nodes from .config import ( PUBLIC_HOST, NODE_PRIVACY, NODE_IS_BOOTSTRAP, MAX_CONTENT_SIZE_MB, IPFS_ANNOUNCE_ADDRESSES, ) from app.core._config import ALLOWED_CONTENT_TYPES from .constants import NODE_TYPE_PUBLIC from app.core.ipfs_client import id_info START_TS = time.time() async def _metrics(session) -> Dict[str, Any]: # Lightweight metrics for handshake # Count total content (any type) total_contents = (await session.execute(select(StoredContent))).scalars().all() content_count = len(total_contents) # Basic system metrics try: load1, load5, load15 = os.getloadavg() except Exception: load1 = load5 = load15 = 0.0 try: from app.core._config import UPLOADS_DIR du = shutil.disk_usage(UPLOADS_DIR) disk_total_gb = round(du.total / (1024 ** 3), 2) disk_free_gb = round(du.free / (1024 ** 3), 2) except Exception: disk_total_gb = disk_free_gb = -1 uptime_sec = int(time.time() - START_TS) return { "content_count": content_count, "uptime_sec": uptime_sec, "loadavg": [load1, load5, load15], "disk_total_gb": disk_total_gb, "disk_free_gb": disk_free_gb, } def _sign(obj: Dict[str, Any]) -> str: signer = Signer(hot_seed) blob = json.dumps(obj, sort_keys=True, separators=(",", ":")).encode() return signer.sign(blob) async def _local_ipfs_payload() -> Dict[str, Any]: try: info = await id_info() multiaddrs = info.get("Addresses") or [] peer_id = info.get("ID") agent = info.get("AgentVersion") except Exception: multiaddrs = IPFS_ANNOUNCE_ADDRESSES or [] peer_id = None agent = None if not multiaddrs: multiaddrs = IPFS_ANNOUNCE_ADDRESSES or [] payload: Dict[str, Any] = {} if multiaddrs: payload["multiaddrs"] = multiaddrs if peer_id: payload["peer_id"] = peer_id if agent: payload["agent_version"] = agent return payload async def build_handshake_payload(session) -> Dict[str, Any]: ipfs_payload = await _local_ipfs_payload() payload = { "version": CURRENT_PROTOCOL_VERSION, "public_key": b58encode(hot_pubkey).decode(), # public_host is optional for private nodes **({"public_host": PUBLIC_HOST} if PUBLIC_HOST else {}), "node_type": NODE_PRIVACY if NODE_PRIVACY != NODE_TYPE_PUBLIC else NODE_TYPE_PUBLIC, "metrics": await _metrics(session), "capabilities": { "accepts_inbound": NODE_PRIVACY == NODE_TYPE_PUBLIC, "is_bootstrap": NODE_IS_BOOTSTRAP, "supported_types": ALLOWED_CONTENT_TYPES, "max_content_size_mb": MAX_CONTENT_SIZE_MB, }, "timestamp": int(datetime.utcnow().timestamp()), "nonce": secrets.token_hex(16), } if ipfs_payload.get("multiaddrs"): payload["ipfs"] = ipfs_payload try: payload["known_public_nodes"] = await list_known_public_nodes(session) except Exception: payload["known_public_nodes"] = [] payload["signature"] = _sign(payload) return payload async def compute_node_info(session) -> Dict[str, Any]: ipfs_payload = await _local_ipfs_payload() node_info = { "id": b58encode(hot_pubkey).decode(), "public_key": b58encode(hot_pubkey).decode(), **({"public_host": PUBLIC_HOST} if PUBLIC_HOST else {}), "version": CURRENT_PROTOCOL_VERSION, "node_type": NODE_PRIVACY, "metrics": await _metrics(session), "capabilities": { "accepts_inbound": NODE_PRIVACY == NODE_TYPE_PUBLIC, "is_bootstrap": NODE_IS_BOOTSTRAP, "supported_types": ALLOWED_CONTENT_TYPES, "max_content_size_mb": MAX_CONTENT_SIZE_MB, }, } if ipfs_payload.get("multiaddrs"): node_info["ipfs"] = ipfs_payload return node_info def sign_response(data: Dict[str, Any]) -> Dict[str, Any]: body = { **data, "timestamp": int(datetime.utcnow().timestamp()), } sig = _sign(body) body["server_public_key"] = b58encode(hot_pubkey).decode() body["server_signature"] = sig return body