Разработка системы детекции аномалий DeFi-протокола
Большинство DeFi эксплойтов длятся от одной транзакции до нескольких минут. Euler Finance ($197M) — атака заняла 5 транзакций за ~45 секунд. Curve ($62M) — несколько транзакций в течение часа. Если система мониторинга не реагирует в этом окне — средства потеряны безвозвратно. Задача anomaly detection: поймать атаку в процессе или до неё, дать время на паузу протокола.
Это не просто дашборд с графиками. Это продуктивная система с alert-логикой, circuit breaker-ами и четкими playbook-ами реагирования.
Архитектура системы мониторинга
Уровни наблюдения
Система строится на трёх уровнях, каждый с разным временем реакции:
Уровень 1 — on-chain real-time (< 1 блок). Мониторинг pending транзакций в mempool на подозрительные паттерны. Технически сложно (нужен доступ к private mempool данным), но даёт самое раннее предупреждение.
Уровень 2 — on-chain per-block (< 15 секунд на Ethereum). Анализ каждого нового блока: события протокола, изменения состояния, аномальные объёмы. Основной уровень мониторинга.
Уровень 3 — off-chain aggregated (минуты-часы). Агрегация данных, trend analysis, cross-protocol корреляции. Выявляет медленно развивающиеся атаки и аномальные паттерны поведения.
Компоненты системы
┌─────────────────────┐
│ Event Indexer │
│ (The Graph / own) │
└──────────┬──────────┘
│ events stream
┌──────────▼──────────┐
│ Anomaly Detector │
│ (Rules Engine + │
│ ML models) │
└──────────┬──────────┘
┌──────────┴──────────┐
┌──────▼──────┐ ┌─────────▼────────┐
│ Alert System │ │ Circuit Breaker │
│ (PagerDuty │ │ (auto-pause) │
│ Telegram) │ └────────────────── │
└─────────────┘
On-chain circuit breaker
Самый ценный компонент — возможность автоматически или полуавтоматически паузить протокол при обнаружении атаки.
Pausable с rate limiting
Стандартный Pausable (OZ) — бинарный: on/off. Более гибкий подход: rate limiting на уровне контракта, который срабатывает автоматически при аномальном объёме:
contract ProtocolWithCircuitBreaker is Pausable, AccessControl {
bytes32 public constant GUARDIAN_ROLE = keccak256("GUARDIAN_ROLE");
// Rate limiting parameters
uint256 public maxWithdrawPerBlock; // максимум вывода за блок
uint256 public maxWithdrawPerHour; // максимум за час
uint256 private _withdrawnThisBlock;
uint256 private _withdrawnThisHour;
uint256 private _lastBlockNumber;
uint256 private _lastHourTimestamp;
modifier withinRateLimit(uint256 amount) {
_updateRateLimitCounters();
require(
_withdrawnThisBlock + amount <= maxWithdrawPerBlock,
"Block rate limit exceeded"
);
require(
_withdrawnThisHour + amount <= maxWithdrawPerHour,
"Hour rate limit exceeded"
);
_withdrawnThisBlock += amount;
_withdrawnThisHour += amount;
_;
}
function withdraw(uint256 amount) external whenNotPaused withinRateLimit(amount) {
// ... логика вывода
}
// Guardian может паузить немедленно
function emergencyPause() external onlyRole(GUARDIAN_ROLE) {
_pause();
emit EmergencyPause(msg.sender, block.timestamp);
}
function _updateRateLimitCounters() private {
if (block.number > _lastBlockNumber) {
_withdrawnThisBlock = 0;
_lastBlockNumber = block.number;
}
if (block.timestamp >= _lastHourTimestamp + 1 hours) {
_withdrawnThisHour = 0;
_lastHourTimestamp = block.timestamp;
}
}
}
Rate limits не блокируют нормальную работу, но останавливают атаку с большими объёмами. Установить лимиты на уровне 2-3x типичного объёма — достаточно высоко, чтобы не мешать, достаточно низко, чтобы поймать drain-атаку.
Автоматическая пауза через off-chain Keeper
OpenZeppelin Defender Autotasks (теперь Defender Actions) — serverless функции, реагирующие на on-chain события:
// Defender Action: мониторинг TVL изменений
const { ethers } = require("ethers");
module.exports = async function(credentials) {
const provider = new ethers.providers.JsonRpcProvider(
credentials.secrets.ALCHEMY_URL
);
const vault = new ethers.Contract(VAULT_ADDRESS, VAULT_ABI, provider);
const currentTVL = await vault.totalAssets();
const previousTVL = await storage.get('previousTVL') || currentTVL;
const dropPercent = (previousTVL - currentTVL) * 100n / previousTVL;
// Если TVL упал более чем на 20% за один блок — пауза
if (dropPercent > 20n) {
const signer = credentials.relayer.getSigner();
const guardian = new ethers.Contract(GUARDIAN_ADDRESS, GUARDIAN_ABI, signer);
await guardian.emergencyPause();
// Отправить alert
await notifySlack(`CRITICAL: TVL dropped ${dropPercent}% — protocol paused`);
}
await storage.put('previousTVL', currentTVL.toString());
};
Альтернативно: Tenderly Web3 Actions с встроенным alerting.
Anomaly detection: правила и модели
Rule-based детекция
Быстро и прозрачно. Набор конкретных условий, каждое соответствует известному паттерну атаки:
Flash loan detector:
IF transaction_uses_flashloan
AND same_block contains protocol_interaction
AND net_token_flow_out_of_protocol > threshold
THEN alert: possible_flash_loan_attack
Large single withdrawal:
IF withdrawal_amount > 10% * current_TVL
THEN alert: large_withdrawal (может быть легитимным whale, но требует проверки)
Price oracle deviation:
IF |onchain_price - reference_cex_price| / reference_price > 5%
THEN alert: oracle_price_deviation
Repeated failed transactions:
IF address sends 10+ failed transactions within 5 minutes
THEN alert: possible_exploit_probing
Реализация: Python worker с web3.py, подписывающийся на WebSocket события протокола. Проверяет каждое событие против набора правил.
Инвариантный мониторинг
Для каждого DeFi протокола существуют математические инварианты, которые всегда должны соблюдаться. Мониторинг инвариантов — элегантный способ поймать эксплойт:
Lending протокол:
-
totalDebt <= totalDeposits + accruedInterest -
sum(userBalances) == totalSupply(для share token) -
healthFactor(position) >= 1.0для всех позиций, не находящихся под ликвидацией
AMM:
-
reserve0 * reserve1 >= k(constant product invariant) - Для Curve stableswap:
Dмонотонно растёт при добавлении ликвидности
async def check_invariants(block_number: int):
# Пример для lending протокола
total_debt = await lending.functions.totalBorrows().call(block_identifier=block_number)
total_deposits = await lending.functions.totalDeposits().call(block_identifier=block_number)
accrued = await lending.functions.accruedInterest().call(block_identifier=block_number)
if total_debt > total_deposits + accrued:
await alert_critical(
f"INVARIANT VIOLATED at block {block_number}: "
f"totalDebt ({total_debt}) > totalDeposits ({total_deposits}) + interest ({accrued})"
)
Нарушение инварианта = что-то пошло не так. Либо баг в коде, либо активная атака.
ML-подход для аномального поведения
Rule-based системы хороши для известных паттернов. ML помогает поймать неизвестные атаки через аномальность поведения.
Isolation Forest для детекции аномальных транзакций:
from sklearn.ensemble import IsolationForest
import numpy as np
# Признаки транзакции
def extract_features(tx):
return [
tx['gas_used'],
tx['value'],
len(tx['internal_calls']),
tx['unique_contracts_called'],
tx['token_transfers_count'],
tx['time_since_last_tx_from_address'],
]
# Обучение на исторических данных (нормальные транзакции)
model = IsolationForest(contamination=0.01, random_state=42)
model.fit(normal_transactions_features)
# Скоринг новых транзакций
def score_transaction(tx):
features = extract_features(tx)
score = model.score_samples([features])[0] # более отрицательное = более аномальное
return score
# Пороговое значение определяется эмпирически
ANOMALY_THRESHOLD = -0.5
Проблема ML: много false positives при необычной, но легитимной активности (крупный whale, new protocol interaction). Нужна комбинация: ML для первичного флага + rule-based для верификации + human review для принятия решения о паузе.
Alerting и incident response
Уровни severity для алертов
Не все аномалии требуют немедленного действия:
| Severity | Критерий | Реакция | Время реакции |
|---|---|---|---|
| P1 Critical | Активная атака, потеря funds | Немедленная пауза + звонки команде | < 2 минуты |
| P2 High | Нарушение инварианта, oracle манипуляция | Пауза + ревью в течение часа | < 15 минут |
| P3 Medium | Аномальный объём, необычное поведение | Ревью следующего дня | < 4 часа |
| P4 Low | Статистическое отклонение | Еженедельный ревью | Async |
PagerDuty + Telegram интеграция
P1/P2 алерты: PagerDuty с on-call rotation — гарантирует что кто-то проснётся в 3 ночи. P3 алерты: Telegram group для команды безопасности. P4: дейли Slack digest.
import requests
def alert_p1_critical(message: str, tx_hash: str = None):
# PagerDuty
requests.post(
'https://events.pagerduty.com/v2/enqueue',
json={
'routing_key': PAGERDUTY_KEY,
'event_action': 'trigger',
'payload': {
'summary': f'[CRITICAL] {message}',
'severity': 'critical',
'custom_details': {'tx_hash': tx_hash, 'timestamp': time.time()}
}
}
)
# Telegram
bot.send_message(
chat_id=SECURITY_CHAT_ID,
text=f"🚨 CRITICAL ALERT\n{message}\nTx: {tx_hash}\nTime: {datetime.now()}",
parse_mode='HTML'
)
Incident Response Playbook
Документ с чёткими шагами для каждого сценария атаки. Пример для "TVL drop > 20% in 1 block":
- On-call дежурный получает PagerDuty alert (< 2 мин)
- Проверить Etherscan: найти транзакцию, причину TVL drop
- Если это эксплойт — вызвать
emergencyPause()через Defender Relayer - Уведомить команду в Signal (не в публичный Telegram — не давать attackers время)
- Через 15 минут: публичное сообщение пользователям о паузе
- Post-mortem анализ: как атака прошла, как fix, когда unpause
Playbook должен быть протестирован на drill exercises — имитация инцидента в тестовой среде.
Стек и сроки
Мониторинг инфраструктура: Python 3.11 + web3.py или TypeScript + viem для event subscriptions. PostgreSQL для хранения исторических данных. Redis для real-time counters. Docker + Kubernetes для надёжного деплоя.
Alerting: PagerDuty + Telegram Bot + Slack integration. OpenZeppelin Defender для on-chain Autotasks и Relayer (automated pause).
ML компонент: scikit-learn для Isolation Forest, Apache Kafka для real-time event streaming если объём высокий.
| Компонент | Технология | Время разработки |
|---|---|---|
| Event indexer | web3.py / viem subscriptions | 1-2 недели |
| Rule-based detector | Python rules engine | 1-2 недели |
| Invariant monitor | Python + contract calls | 1 неделя |
| Circuit breaker contract | Solidity + OZ Pausable | 1 неделя |
| Alerting pipeline | PagerDuty + Telegram | 3-5 дней |
| ML anomaly detection | scikit-learn | 2-3 недели |
| Defender Autotasks | JavaScript + Defender SDK | 1 неделя |
| Dashboard | Grafana + InfluxDB | 1-2 недели |
MVP система (rule-based + alerting + basic circuit breaker) — 4-6 недель. Полная система с ML, автоматической паузой, dashboard и протестированными playbook-ами — 10-14 недель.
Стоимость рассчитывается после анализа архитектуры протокола и определения критических инвариантов.







