uploader-bot/app/api/node_communication.py

378 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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)