Разработка системы агрегации данных из блокчейнов
Задача выглядит просто: "собирать данные из нескольких блокчейнов". На практике это одна из самых технически сложных задач в Web3-инфраструктуре. Каждая сеть — это отдельная модель данных, своя логика финализации, свой RPC API, свои rate limits и свои специфические ошибки. Ethereum живёт в UTC с ~12-секундными блоками, Solana выдаёт ~400ms слоты и считает подтверждения иначе, TON имеет шардированную архитектуру где "блок" — понятие условное. Собрать всё это в единый API с консистентными данными — нетривиальная инженерная задача.
Проблема гетерогенности: почему нельзя просто "опросить все RPC"
Различные модели данных
EVM-сети (Ethereum, Arbitrum, Polygon, BSC) имеют общую модель: блоки, транзакции, receipts с logs. Но даже здесь есть различия:
-
Arbitrum добавляет
l1BlockNumberи специфические системные транзакции (sequencer batch submissions) -
Optimism/Base имеют
depositedTxтип для L1→L2 транзакций, которые не имеют стандартногоfrom - zkSync Era использует Native AA — нет разделения на EOA и контракты, все аккаунты контракты
Solana вообще другая парадигма: нет "транзакции вызвала метод контракта" — есть "инструкции в транзакции передались программам". Для декодирования нужен ABI аналог — IDL (Interface Definition Language, формат Anchor).
UTXO-модели (Bitcoin, Litecoin) принципиально отличаются: нет балансов аккаунтов, есть unspent outputs. "Баланс адреса" — это сумма всех UTXO где этот адрес является output.
Различная семантика финализации
| Сеть | Механизм | Финализация |
|---|---|---|
| Ethereum | PoS + Casper FFG | ~15 мин (finalized checkpoint) |
| Arbitrum One | Optimistic Rollup | ~7 дней (fraud proof window) для L1 finality |
| Polygon PoS | Heimdall checkpoints | ~30 мин для Ethereum finality |
| Solana | Tower BFT | ~12-32 слота (~6–16 сек) |
| Bitcoin | PoW | 6 подтверждений (~60 мин) — конвенциональный стандарт |
Если система не учитывает это, данные будут некорректны: транзакция покажется "финальной" по числу подтверждений, но окажется реорганизована.
Архитектура системы агрегации
Слой коллекторов (Chain Collectors)
Каждый коллектор — изолированный сервис, отвечающий за одну сеть. Общий интерфейс:
interface ChainCollector {
getLatestBlock(): Promise<UnifiedBlock>;
getBlockRange(from: bigint, to: bigint): Promise<UnifiedBlock[]>;
getTransactionsByAddress(address: string, fromBlock: bigint): Promise<UnifiedTx[]>;
subscribeNewBlocks(callback: (block: UnifiedBlock) => void): Unsubscribe;
}
Унифицированные типы нормализуют специфику каждой сети:
interface UnifiedTx {
chain: ChainId;
hash: string;
blockNumber: bigint;
timestamp: number; // unix
from: string; // normalized lowercase hex для EVM, base58 для Solana
to: string | null;
value: bigint; // в наименьших единицах нативного токена
status: 'success' | 'failed' | 'pending';
finality: 'unconfirmed' | 'safe' | 'finalized';
raw: unknown; // оригинальные данные сети
}
Управление нодами и провайдерами
Проблема: публичные RPC ненадёжны, rate limits непредсказуемы, Alchemy/Infura дорожают с масштабом.
Стратегия: tiered provider pool
Primary: Собственные ноды (Geth+Lighthouse, Reth для архива)
↓ failover
Secondary: Alchemy / QuickNode (premium tier)
↓ failover
Tertiary: Infura / публичные RPC (только для некритичных запросов)
Circuit breaker на каждом провайдере: если error rate > 5% за 60 сек или latency > 2x p99 baseline — выключаем провайдер из rotation, health check каждые 30 сек.
Для архивных данных (исторические блоки > 128 blocks назад на Ethereum) нужна archive node — это отдельная история. Erigon занимает ~3TB для полного Ethereum архива, Reth немного меньше. Для большинства проектов дешевле использовать Alchemy Archive или QuickNode Archive чем держать собственную ноду.
Слой нормализации и трансформации
Сырые блокчейн-данные редко нужны в исходном виде. Типичные трансформации:
Декодирование ERC-20 Transfer событий
const ERC20_TRANSFER_TOPIC =
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
function decodeTransfer(log: Log): TokenTransfer | null {
if (log.topics[0] !== ERC20_TRANSFER_TOPIC) return null;
return {
token: log.address,
from: `0x${log.topics[1].slice(26)}`,
to: `0x${log.topics[2].slice(26)}`,
amount: BigInt(log.data),
};
}
Обогащение данными токена: для каждого log.address нужно знать symbol, decimals, USD price. Кешируем metadata токенов в Redis с TTL 24h, цены обновляем каждые 30 сек из CoinGecko/CoinMarketCap.
Агрегация cross-chain: если нужно показать "суммарный баланс адреса во всех сетях в USD", нужно нормализовать разные decimals, конвертировать через price feeds, обработать wrapped-версии одного токена (USDC на Ethereum ≠ USDC.e на Arbitrum).
Слой хранения
Для hot данных (последние 7–30 дней): PostgreSQL с партиционированием по chain_id + дате. Индексы на (chain_id, address, block_number) и (chain_id, tx_hash). TimescaleDB гипертаблицы если данных много — автоматическая компрессия старых партиций.
Для cold данных (архив): ClickHouse — колончатая БД, на порядок эффективнее PostgreSQL для аналитических запросов по большим периодам. Запрос "все транзакции USDC > $10k за 2023 год по всем EVM сетям" на 100M+ строках — ClickHouse даст результат за секунды, PostgreSQL — за минуты.
Для поиска по адресам/хешам: ElasticSearch или просто PostgreSQL с LIKE — для точных совпадений достаточно hash-индекса.
Обработка реорганизаций
Это самое сложное место в системе. Алгоритм:
- Каждый блок сохраняем с флагом
is_canonical = trueиparent_hash - Новый блок с тем же
block_numberно другимhash— потенциальный реорг - Идём по
parent_hashназад до нахождения общего предка - Все блоки на "старой" ветке помечаем
is_canonical = false, добавляем блоки "новой" ветки - Данные в выходном API всегда фильтруются по
is_canonical = true - Webhooks/downstream системы получают события
tx.orphanedдля отозванных транзакций
Для Ethereum глубина реорга крайне редко > 2 блоков post-Merge. Для Polygon PoS — видели реорги на 30+ блоков. Буфер наблюдения: 128 блоков для EVM сетей.
API слой
REST + WebSocket для real-time:
GET /v1/address/{address}/transactions?chains=eth,arb,polygon&limit=50
GET /v1/tx/{chain}/{hash}
GET /v1/address/{address}/token-balances?chains=eth,bsc
WS /v1/subscribe?address={addr}&chains=eth,arb&events=transfer,swap
GraphQL удобен если клиентам нужна гибкость в запросах: один запрос получает транзакции + балансы + metadata токенов. Но добавляет сложность на бэкенде — N+1 проблемы, need DataLoader.
Rate limiting: per-API-key, sliding window, отдельные лимиты для REST и WebSocket (WebSocket connections дороже). Redis + Lua script для атомарных инкрементов.
Мониторинг и операционка
Критические метрики:
-
Collector lag — разница между
latest block timestampв сети и временем обработки этого блока в нашей системе. Alert при lag > 2 минут. - Reorg depth — максимальная глубина реорга за последние 24h. Alert при depth > 10.
- RPC error rate — по каждому провайдеру и методу. Alert при > 1%.
- Queue depth — если обработчик не успевает за коллектором, очередь растёт. Alert при depth > 10k сообщений.
Grafana дашборд с per-chain панелями: текущий блок, lag, TPS, error rate.
Стек
| Компонент | Технология |
|---|---|
| Коллекторы | Node.js (viem/ethers) + Go для высоконагруженных сетей |
| Очередь | Apache Kafka (высокий throughput) или RabbitMQ (moderate) |
| Hot storage | PostgreSQL 15 + TimescaleDB |
| Cold storage | ClickHouse |
| Cache | Redis Cluster |
| API | Node.js (Fastify) или Go (Fiber) |
| Monitoring | Prometheus + Grafana + PagerDuty |
| Оркестрация | Kubernetes с HPA на коллекторах |
Реалистичный срок MVP (3–4 EVM сети, без архива, REST API): 8–12 недель. Полная система с 10+ сетями, ClickHouse, WebSocket, мониторингом — 5–7 месяцев.







