""" API endpoints для межузлового общения с ed25519 подписями """ import json from typing import Dict, Any, Optional from datetime import datetime from sanic import Blueprint, Request from sanic.response import json as json_response from app.core.crypto import get_ed25519_manager from app.core.logging import get_logger from app.api.middleware import auth_required, validate_json logger = get_logger(__name__) # Blueprint для межузловых коммуникаций node_bp = Blueprint("node", url_prefix="/api/node") async def validate_node_request(request: Request) -> Dict[str, Any]: """Валидация межузлового запроса с обязательной проверкой подписи""" # Проверяем наличие обязательных заголовков required_headers = ["X-Node-Communication", "X-Node-ID", "X-Node-Public-Key", "X-Node-Signature"] for header in required_headers: if not request.headers.get(header): raise ValueError(f"Missing required header: {header}") # Проверяем, что это межузловое общение if request.headers.get("X-Node-Communication") != "true": raise ValueError("Not a valid inter-node communication") # Информация о ноде уже проверена в middleware node_id = request.ctx.source_node_id public_key = request.ctx.source_public_key # Получаем данные сообщения if not hasattr(request, 'json') or not request.json: raise ValueError("Empty message body") return { "node_id": node_id, "public_key": public_key, "message": request.json } async def create_node_response(data: Dict[str, Any]) -> Dict[str, Any]: """Создать ответ для межузлового общения с подписью""" crypto_manager = get_ed25519_manager() # Добавляем информацию о нашей ноде response_data = { "success": True, "timestamp": datetime.utcnow().isoformat(), "node_id": crypto_manager.node_id, "data": data } return response_data @node_bp.route("/handshake", methods=["POST"]) async def node_handshake(request: Request): """ Обработка хэндшейка между нодами Ожидаемый формат сообщения: { "action": "handshake", "node_info": { "node_id": "...", "version": "...", "capabilities": [...], "network_info": {...} }, "timestamp": "..." } """ try: # Валидация межузлового запроса node_data = await validate_node_request(request) message = node_data["message"] source_node_id = node_data["node_id"] logger.info(f"Handshake request from node {source_node_id}") # Проверяем формат сообщения хэндшейка if message.get("action") != "handshake": return json_response({ "success": False, "error": "Invalid handshake message format" }, status=400) node_info = message.get("node_info", {}) if not node_info.get("node_id") or not node_info.get("version"): return json_response({ "success": False, "error": "Missing required node information" }, status=400) # Создаем информацию о нашей ноде для ответа crypto_manager = get_ed25519_manager() our_node_info = { "node_id": crypto_manager.node_id, "version": "3.0.0", # Версия MY Network "capabilities": [ "content_upload", "content_sync", "decentralized_filtering", "ed25519_signatures" ], "network_info": { "public_key": crypto_manager.public_key_hex, "protocol_version": "1.0" } } # Сохраняем информацию о ноде (здесь можно добавить в базу данных) logger.info(f"Successful handshake with node {source_node_id}", extra={"peer_node_info": node_info}) response_data = await create_node_response({ "handshake_accepted": True, "node_info": our_node_info }) return json_response(response_data) except ValueError as e: logger.warning(f"Invalid handshake request: {e}") return json_response({ "success": False, "error": str(e) }, status=400) except Exception as e: logger.error(f"Handshake error: {e}") return json_response({ "success": False, "error": "Internal server error" }, status=500) @node_bp.route("/content/sync", methods=["POST"]) async def content_sync(request: Request): """ Синхронизация контента между нодами Ожидаемый формат сообщения: { "action": "content_sync", "sync_type": "new_content|content_list|content_request", "content_info": {...}, "timestamp": "..." } """ try: # Валидация межузлового запроса node_data = await validate_node_request(request) message = node_data["message"] source_node_id = node_data["node_id"] logger.info(f"Content sync request from node {source_node_id}") # Проверяем формат сообщения синхронизации if message.get("action") != "content_sync": return json_response({ "success": False, "error": "Invalid sync message format" }, status=400) sync_type = message.get("sync_type") content_info = message.get("content_info", {}) if sync_type == "new_content": # Обработка нового контента от другой ноды content_hash = content_info.get("hash") if not content_hash: return json_response({ "success": False, "error": "Missing content hash" }, status=400) # Здесь добавить логику обработки нового контента # через decentralized_filter и content_storage_manager response_data = await create_node_response({ "sync_result": "content_accepted", "content_hash": content_hash }) elif sync_type == "content_list": # Запрос списка доступного контента # Здесь добавить логику получения списка контента response_data = await create_node_response({ "content_list": [], # Заглушка - добавить реальный список "total_items": 0 }) elif sync_type == "content_request": # Запрос конкретного контента requested_hash = content_info.get("hash") if not requested_hash: return json_response({ "success": False, "error": "Missing content hash for request" }, status=400) # Здесь добавить логику поиска и передачи контента response_data = await create_node_response({ "content_found": False, # Заглушка - добавить реальную проверку "content_hash": requested_hash }) else: return json_response({ "success": False, "error": f"Unknown sync type: {sync_type}" }, status=400) return json_response(response_data) except ValueError as e: logger.warning(f"Invalid sync request: {e}") return json_response({ "success": False, "error": str(e) }, status=400) except Exception as e: logger.error(f"Content sync error: {e}") return json_response({ "success": False, "error": "Internal server error" }, status=500) @node_bp.route("/network/ping", methods=["POST"]) async def network_ping(request: Request): """ Пинг между нодами для проверки доступности Ожидаемый формат сообщения: { "action": "ping", "timestamp": "...", "data": {...} } """ try: # Валидация межузлового запроса node_data = await validate_node_request(request) message = node_data["message"] source_node_id = node_data["node_id"] logger.debug(f"Ping from node {source_node_id}") # Проверяем формат пинга if message.get("action") != "ping": return json_response({ "success": False, "error": "Invalid ping message format" }, status=400) # Создаем ответ pong response_data = await create_node_response({ "action": "pong", "ping_timestamp": message.get("timestamp"), "response_timestamp": datetime.utcnow().isoformat() }) return json_response(response_data) except ValueError as e: logger.warning(f"Invalid ping request: {e}") return json_response({ "success": False, "error": str(e) }, status=400) except Exception as e: logger.error(f"Ping error: {e}") return json_response({ "success": False, "error": "Internal server error" }, status=500) @node_bp.route("/network/status", methods=["GET"]) async def network_status(request: Request): """ Получение статуса ноды (без обязательной подписи для GET запросов) """ try: crypto_manager = get_ed25519_manager() status_data = { "node_id": crypto_manager.node_id, "public_key": crypto_manager.public_key_hex, "version": "3.0.0", "status": "active", "capabilities": [ "content_upload", "content_sync", "decentralized_filtering", "ed25519_signatures" ], "timestamp": datetime.utcnow().isoformat() } return json_response({ "success": True, "data": status_data }) except Exception as e: logger.error(f"Status error: {e}") return json_response({ "success": False, "error": "Internal server error" }, status=500) @node_bp.route("/network/discover", methods=["POST"]) async def network_discover(request: Request): """ Обнаружение и обмен информацией о других нодах в сети Ожидаемый формат сообщения: { "action": "discover", "known_nodes": [...], "timestamp": "..." } """ try: # Валидация межузлового запроса node_data = await validate_node_request(request) message = node_data["message"] source_node_id = node_data["node_id"] logger.info(f"Discovery request from node {source_node_id}") # Проверяем формат сообщения if message.get("action") != "discover": return json_response({ "success": False, "error": "Invalid discovery message format" }, status=400) known_nodes = message.get("known_nodes", []) # Здесь добавить логику обработки информации о известных нодах # и возврат информации о наших известных нодах response_data = await create_node_response({ "known_nodes": [], # Заглушка - добавить реальный список "discovery_timestamp": datetime.utcnow().isoformat() }) return json_response(response_data) except ValueError as e: logger.warning(f"Invalid discovery request: {e}") return json_response({ "success": False, "error": str(e) }, status=400) except Exception as e: logger.error(f"Discovery error: {e}") return json_response({ "success": False, "error": "Internal server error" }, status=500)