406 lines
11 KiB
Markdown
406 lines
11 KiB
Markdown
|
|
# Montana Protocol — P2P Network Specification
|
|||
|
|
# Техническая спецификация пиринговой сети
|
|||
|
|
|
|||
|
|
**Version:** 2.0
|
|||
|
|
**Status:** MAINNET
|
|||
|
|
**Date:** 2026-01-23
|
|||
|
|
**Authors:** Alejandro Montana
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Abstract
|
|||
|
|
|
|||
|
|
Настоящий документ описывает архитектуру пиринговой сети Montana Protocol. Сеть обеспечивает распространение подписей присутствия, выбор активного мастера и защиту от изоляции узлов.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. Архитектура сети
|
|||
|
|
|
|||
|
|
### 1.1 Топология
|
|||
|
|
|
|||
|
|
Montana Protocol использует фиксированную топологию из 5 узлов:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌─────────────┐
|
|||
|
|
│ amsterdam │ ← PRIMARY (позиция 0)
|
|||
|
|
│ 72.56.102.240│
|
|||
|
|
└──────┬──────┘
|
|||
|
|
│
|
|||
|
|
┌─────────────────┼─────────────────┐
|
|||
|
|
│ │ │
|
|||
|
|
┌────▼────┐ ┌────▼────┐ ┌─────▼────┐
|
|||
|
|
│ moscow │ │ almaty │ │ spb │
|
|||
|
|
│176.124..│ │91.200...│ │188.225...│
|
|||
|
|
└────┬────┘ └────┬────┘ └────┬─────┘
|
|||
|
|
│ │ │
|
|||
|
|
└────────────────┼────────────────┘
|
|||
|
|
│
|
|||
|
|
┌──────▼──────┐
|
|||
|
|
│ novosibirsk │
|
|||
|
|
│147.45.147...│
|
|||
|
|
└─────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 1.2 Типы узлов
|
|||
|
|
|
|||
|
|
| Тип | Описание | Реализация |
|
|||
|
|
|-----|----------|------------|
|
|||
|
|
| **Full Node** | Полный узел сети с Telegram polling | junomontanaagibot.py |
|
|||
|
|
| **Health Server** | HTTP endpoint для проверки здоровья | leader_election.py:8889 |
|
|||
|
|
|
|||
|
|
### 1.3 Порты
|
|||
|
|
|
|||
|
|
| Порт | Протокол | Назначение |
|
|||
|
|
|------|----------|------------|
|
|||
|
|
| 22 | SSH | Администрирование |
|
|||
|
|
| 8889 | HTTP | Health check endpoint |
|
|||
|
|
| 443 | HTTPS | Fallback проверка |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. Leader Election Protocol
|
|||
|
|
|
|||
|
|
### 2.1 Алгоритм выбора мастера
|
|||
|
|
|
|||
|
|
Детерминированный выбор мастера на основе позиции в цепочке:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# leader_election.py:326-349
|
|||
|
|
|
|||
|
|
BOT_CHAIN = [
|
|||
|
|
("amsterdam", "72.56.102.240"), # позиция 0
|
|||
|
|
("moscow", "176.124.208.93"), # позиция 1
|
|||
|
|
("almaty", "91.200.148.93"), # позиция 2
|
|||
|
|
("spb", "188.225.58.98"), # позиция 3
|
|||
|
|
("novosibirsk", "147.45.147.247"), # позиция 4
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
def am_i_the_master():
|
|||
|
|
for name, ip in BOT_CHAIN:
|
|||
|
|
if name == my_node_name:
|
|||
|
|
return True # Я первый живой — я мастер
|
|||
|
|
if check_node_health(ip):
|
|||
|
|
return False # Узел выше меня жив
|
|||
|
|
return False
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.2 Health Check Protocol
|
|||
|
|
|
|||
|
|
HTTP запрос к `/health` endpoint:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# leader_election.py:102-134
|
|||
|
|
|
|||
|
|
def check_node_health(ip: str) -> bool:
|
|||
|
|
try:
|
|||
|
|
url = f"http://{ip}:8889/health"
|
|||
|
|
response = requests.get(url, timeout=5)
|
|||
|
|
return response.status_code == 200
|
|||
|
|
except:
|
|||
|
|
return False
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Response format:**
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"status": "healthy",
|
|||
|
|
"node": "moscow",
|
|||
|
|
"uptime": 3600,
|
|||
|
|
"is_master": false
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.3 Failover Timing
|
|||
|
|
|
|||
|
|
| Событие | Время |
|
|||
|
|
|---------|-------|
|
|||
|
|
| Health check interval | 5 секунд |
|
|||
|
|
| Request timeout | 5 секунд |
|
|||
|
|
| Max failover time | < 10 секунд |
|
|||
|
|
| Startup delay | 7-15 секунд (рандом) |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. Message Types
|
|||
|
|
|
|||
|
|
### 3.1 Telegram Polling
|
|||
|
|
|
|||
|
|
Только мастер-узел выполняет polling Telegram API:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# junomontanaagibot.py
|
|||
|
|
|
|||
|
|
async def start_polling():
|
|||
|
|
if not am_i_the_master():
|
|||
|
|
return # Standby режим
|
|||
|
|
|
|||
|
|
await application.run_polling(
|
|||
|
|
drop_pending_updates=True,
|
|||
|
|
allowed_updates=Update.ALL_TYPES
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.2 Presence Proof
|
|||
|
|
|
|||
|
|
Подпись присутствия (ML-DSA-65):
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# time_bank.py:271-327
|
|||
|
|
|
|||
|
|
message = f"MONTANA_PRESENCE_V1:{timestamp}:{prev_hash}:{pubkey}:{t2_index}"
|
|||
|
|
signature = ML_DSA_65.sign(private_key, message.encode())
|
|||
|
|
proof_hash = SHA256(message || signature)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
| Поле | Размер | Описание |
|
|||
|
|
|------|--------|----------|
|
|||
|
|
| version | 20 B | "MONTANA_PRESENCE_V1" |
|
|||
|
|
| timestamp | 8 B | Unix timestamp |
|
|||
|
|
| prev_hash | 64 B | SHA256 предыдущего proof |
|
|||
|
|
| pubkey | 1952 B | ML-DSA-65 public key |
|
|||
|
|
| t2_index | 8 B | Номер τ₂ слайса |
|
|||
|
|
| signature | 3309 B | ML-DSA-65 подпись |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. Защита от Eclipse
|
|||
|
|
|
|||
|
|
### 4.1 Стратегия
|
|||
|
|
|
|||
|
|
Eclipse-атака — изоляция узла от честной сети. Защита обеспечивается:
|
|||
|
|
|
|||
|
|
1. **Фиксированная топология** — узлы знают друг друга заранее
|
|||
|
|
2. **Множественные проверки** — ping + TCP fallback
|
|||
|
|
3. **Географическое распределение** — 5 регионов
|
|||
|
|
|
|||
|
|
### 4.2 Multi-method Health Check
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# leader_election.py:102-134
|
|||
|
|
|
|||
|
|
def check_node_health(ip: str) -> bool:
|
|||
|
|
# 1. HTTP health endpoint
|
|||
|
|
try:
|
|||
|
|
response = requests.get(f"http://{ip}:8889/health", timeout=5)
|
|||
|
|
if response.status_code == 200:
|
|||
|
|
return True
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
# 2. Fallback: TCP port check (SSH)
|
|||
|
|
try:
|
|||
|
|
sock = socket.create_connection((ip, 22), timeout=5)
|
|||
|
|
sock.close()
|
|||
|
|
return True
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
# 3. Fallback: TCP port check (HTTPS)
|
|||
|
|
try:
|
|||
|
|
sock = socket.create_connection((ip, 443), timeout=5)
|
|||
|
|
sock.close()
|
|||
|
|
return True
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
return False
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. Защита от атак
|
|||
|
|
|
|||
|
|
### 5.1 Random Failover
|
|||
|
|
|
|||
|
|
При обнаружении атаки цепочка перемешивается:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# leader_election.py:395-430
|
|||
|
|
|
|||
|
|
def shuffle_chain_on_attack():
|
|||
|
|
if not attack_detector.is_under_attack():
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
shuffled = list(BOT_CHAIN)
|
|||
|
|
random.shuffle(shuffled)
|
|||
|
|
|
|||
|
|
healthy_nodes = [
|
|||
|
|
(name, ip) for name, ip in shuffled
|
|||
|
|
if check_node_health(ip)
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
BOT_CHAIN = healthy_nodes # Новый порядок
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Пример:**
|
|||
|
|
```
|
|||
|
|
Нормальный режим:
|
|||
|
|
amsterdam → moscow → almaty → spb → novosibirsk
|
|||
|
|
|
|||
|
|
После атаки:
|
|||
|
|
spb → novosibirsk → almaty → moscow
|
|||
|
|
(amsterdam исключён как атакованный)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.2 Attack Detection Metrics
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# leader_election.py:190-232
|
|||
|
|
|
|||
|
|
class AttackDetector:
|
|||
|
|
cpu_threshold = 80.0 # %
|
|||
|
|
network_threshold = 100 MB/s # входящий трафик
|
|||
|
|
max_failures = 10 # подряд
|
|||
|
|
response_time_threshold = 5.0 # секунд
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. Синхронизация состояния
|
|||
|
|
|
|||
|
|
### 6.1 Breathing Sync
|
|||
|
|
|
|||
|
|
Git-синхронизация каждые 12 секунд:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌─────────┐ ┌─────────┐ ┌─────────┐
|
|||
|
|
│ Inhale │────▶│ Work │────▶│ Exhale │
|
|||
|
|
│git pull │ │ 12 sec │ │git push │
|
|||
|
|
└─────────┘ └─────────┘ └─────────┘
|
|||
|
|
▲ │
|
|||
|
|
└───────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Исходный код:** [breathing_sync.py](../Бот/breathing_sync.py)
|
|||
|
|
|
|||
|
|
### 6.2 Синхронизируемые данные
|
|||
|
|
|
|||
|
|
| Данные | Путь | Формат |
|
|||
|
|
|--------|------|--------|
|
|||
|
|
| База данных | data/montana.db | SQLite |
|
|||
|
|
| События | data/events.jsonl | JSON Lines |
|
|||
|
|
| Конфигурация | *.py | Python |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 7. Сетевые метрики
|
|||
|
|
|
|||
|
|
### 7.1 Health Status
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# junomontanaagibot.py:997-1051
|
|||
|
|
|
|||
|
|
def health_check() -> Dict:
|
|||
|
|
return {
|
|||
|
|
"status": "healthy" | "degraded" | "under_attack",
|
|||
|
|
"uptime": int,
|
|||
|
|
"metrics": {
|
|||
|
|
"cpu_percent": float, # скользящее среднее
|
|||
|
|
"memory_percent": float,
|
|||
|
|
"disk_percent": float,
|
|||
|
|
"active_connections": int,
|
|||
|
|
"blocked_ips": int,
|
|||
|
|
"suspicious_ips": int
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 7.2 Пороги статусов
|
|||
|
|
|
|||
|
|
| Статус | Условие |
|
|||
|
|
|--------|---------|
|
|||
|
|
| healthy | cpu < 90% AND mem < 90% AND suspicious < 5 |
|
|||
|
|
| degraded | (cpu > 90% OR mem > 90%) × 3 подряд |
|
|||
|
|
| under_attack | attack_detector.under_attack = True |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 8. Деплой и обновление
|
|||
|
|
|
|||
|
|
### 8.1 Rolling Deploy
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# deploy_nodes.sh
|
|||
|
|
|
|||
|
|
./deploy_nodes.sh # По одному узлу, 20 сек между ними
|
|||
|
|
./deploy_nodes.sh --fast # Все сразу (downtime!)
|
|||
|
|
./deploy_nodes.sh --node X # Только узел X
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 8.2 Порядок деплоя
|
|||
|
|
|
|||
|
|
1. Обновить amsterdam (PRIMARY)
|
|||
|
|
2. Ждать 20 секунд (failover time)
|
|||
|
|
3. Обновить moscow
|
|||
|
|
4. Ждать 20 секунд
|
|||
|
|
5. ... остальные узлы
|
|||
|
|
|
|||
|
|
### 8.3 Верификация
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# Проверка хэшей на всех узлах
|
|||
|
|
for ip in 72.56.102.240 176.124.208.93 ...; do
|
|||
|
|
ssh root@$ip "md5sum /root/bot/junomontanaagibot.py"
|
|||
|
|
done
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Ожидаемый результат:** все хэши идентичны.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 9. Диагностика
|
|||
|
|
|
|||
|
|
### 9.1 Проверка статуса узла
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
ssh root@IP "systemctl status junona"
|
|||
|
|
ssh root@IP "journalctl -u junona -n 50 --no-pager"
|
|||
|
|
ssh root@IP "curl localhost:8889/health"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 9.2 Проверка leader election
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
ssh root@IP "journalctl -u junona | grep -i 'master\|standby'"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 9.3 Проверка синхронизации
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
for ip in 72.56.102.240 176.124.208.93 91.200.148.93 188.225.58.98 147.45.147.247; do
|
|||
|
|
echo "=== $ip ==="
|
|||
|
|
ssh root@$ip "md5sum /root/bot/junomontanaagibot.py | cut -d' ' -f1"
|
|||
|
|
done
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 10. Ограничения
|
|||
|
|
|
|||
|
|
| Ограничение | Описание | Mitigation |
|
|||
|
|
|-------------|----------|------------|
|
|||
|
|
| Фиксированная топология | Нельзя добавить узел динамически | Требует деплой |
|
|||
|
|
| Single Telegram Token | Один токен на всю сеть | Leader Election |
|
|||
|
|
| Git-based sync | Не подходит для high-frequency данных | Event Sourcing |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Заключение
|
|||
|
|
|
|||
|
|
P2P сеть Montana Protocol обеспечивает:
|
|||
|
|
|
|||
|
|
1. **Отказоустойчивость** — 5 узлов, автоматический failover
|
|||
|
|
2. **Безопасность** — ML-DSA-65, Random Failover при атаке
|
|||
|
|
3. **Консистентность** — Breathing Sync через Git
|
|||
|
|
4. **Мониторинг** — Health endpoints, AtlantGuard
|
|||
|
|
|
|||
|
|
Сеть функционирует в MAINNET режиме с января 2026.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Alejandro Montana
|
|||
|
|
Montana Protocol P2P Specification v2.0
|
|||
|
|
2026-01-23
|
|||
|
|
```
|