from __future__ import annotations import json from datetime import datetime import os import time import shutil import secrets from typing import Dict, Any from app.core._utils.b58 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 app.core.network.dht import compute_node_id, dht_config 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, "schema_version": dht_config.schema_version, "public_key": b58encode(hot_pubkey).decode(), "node_id": compute_node_id(hot_pubkey), # 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(), "schema_version": dht_config.schema_version, "node_id": compute_node_id(hot_pubkey), **({"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