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 from app.core._config import ALLOWED_CONTENT_TYPES from .constants import NODE_TYPE_PUBLIC 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 build_handshake_payload(session) -> Dict[str, Any]: 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), } 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]: 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, }, } 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