149 lines
4.7 KiB
Python
149 lines
4.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,
|
|
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
|