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

154 lines
5.0 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 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