uploader-bot/app/core/network/handshake.py

114 lines
3.7 KiB
Python

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