DevOps для Blockchain: RPC, транзакции и multi-chain мониторинг | AdminWiki

DevOps для блокчейн-сервисов: мониторинг RPC, транзакций и multi-chain операций

18 сентября 2026 10 мин. чтения
Содержание статьи

Почему blockchain-сервис требует отдельного подхода к эксплуатации

С точки зрения DevOps блокчейн-приложение заметно отличается от обычного веб-сервиса. Недостаточно убедиться, что API отвечает кодом 200, база данных доступна, а контейнеры находятся в состоянии Running. Приложение может быть полностью доступно технически, но при этом перестать получать новые блоки, использовать отстающий RPC-узел или считать транзакцию завершенной раньше необходимого количества подтверждений.

Особенно сложной становится multi-chain инфраструктура, где backend одновременно взаимодействует с несколькими сетями. У каждой сети собственная высота блока, комиссии, RPC API, правила подтверждения транзакций и потенциальные сценарии отказа.

Поэтому мониторить нужно не только состояние приложения, но и состояние каждой blockchain-интеграции отдельно.

RPC endpoint - критическая зависимость

Большинство приложений взаимодействуют с блокчейном через RPC. Через него backend получает высоту блока, баланс адреса, информацию о транзакции, состояние smart contract и другие данные.

Если RPC недоступен, приложение может внешне продолжать работать, но перестать выполнять часть бизнес-операций.

Поэтому обычного health check:

GET /health

200 OK

недостаточно.

Полезнее проверять всю цепочку зависимостей:

{
  "status": "degraded",
  "services": {
    "postgres": "ok",
    "redis": "ok",
    "ethereum_rpc": "ok",
    "tron_rpc": "timeout",
    "queue": "ok"
  }
}

Такой ответ сразу показывает, что приложение работает лишь частично.

Какие метрики RPC стоит собирать

Для каждого провайдера или собственной ноды полезно хранить отдельные метрики:

blockchain_rpc_requests_total
blockchain_rpc_errors_total
blockchain_rpc_duration_seconds
blockchain_rpc_timeouts_total
blockchain_rpc_current_block
blockchain_rpc_block_lag
blockchain_rpc_rate_limit_total

Обязательно добавляйте label сети:

blockchain_rpc_requests_total{chain="ethereum"}
blockchain_rpc_requests_total{chain="tron"}
blockchain_rpc_requests_total{chain="bitcoin"}

Если используется несколько RPC endpoints для одной сети, пригодится также имя провайдера:

blockchain_rpc_duration_seconds{
    chain="ethereum",
    provider="primary"
}

Это позволяет увидеть ситуацию, когда проблема существует не в Ethereum как таковом, а у конкретного RPC-провайдера.

Проверяем не только доступность, но и высоту блока

Одна из неприятных ситуаций - RPC отвечает без ошибок, но данные на нем отстают.

Например:

Primary RPC block:   24587120
Secondary RPC block: 24587121
Local node block:    24586940

Локальная нода формально доступна, однако отстает почти на 200 блоков.

Если использовать ее для проверки входящих транзакций, приложение будет показывать устаревшее состояние.

Поэтому полезно рассчитывать отставание ноды:

block_lag =
    reference_block_height -
    current_rpc_block_height

И устанавливать alert:

blockchain_rpc_block_lag{chain="ethereum"} > 10

Конкретный допустимый lag зависит от сети и задачи приложения.

RPC failover: один endpoint - это single point of failure

Production-приложению не стоит полностью зависеть от единственного RPC URL.

В коде можно держать несколько endpoints:

RPC_ENDPOINTS = [
    "https://primary-rpc.example",
    "https://secondary-rpc.example",
    "https://emergency-rpc.example",
]

Однако простой переход на следующий адрес после любой ошибки тоже может создать проблемы. Например, при HTTP 429 лучше временно снизить интенсивность запросов, а не мгновенно направить всю нагрузку на резервный endpoint.

Стоит различать:

  • timeout;
  • connection error;
  • rate limit;
  • ошибку конкретного RPC method;
  • невалидный ответ;
  • отставание ноды по блокам.

Для каждого типа ошибки стратегия failover может отличаться.

Circuit breaker для нестабильного RPC

Если один endpoint начинает отвечать с ошибкой, нет смысла продолжать отправлять ему тысячи запросов.

Можно использовать circuit breaker. Например:

if rpc.failures >= 5:
    rpc.disable_for(seconds=60)

После истечения таймера выполняется тестовый запрос. Если он успешен, endpoint возвращается в пул.

Подобная схема снижает количество бесполезных timeout и уменьшает нагрузку на уже деградировавший сервис.

Multi-chain операции: сеть должна быть частью модели данных

Одна из архитектурных ошибок blockchain-приложений - хранить только обозначение токена.

Например:

asset = "USDT"

Этого недостаточно.

Один и тот же актив может существовать в нескольких сетях, поэтому идентификатор должен учитывать blockchain:

{
  "asset": "USDT",
  "chain": "ethereum"
}

или:

{
  "asset": "USDT",
  "chain": "tron"
}

Для backend это фактически разные сущности: отличаются формат транзакции, комиссии, RPC, explorer и обработчик подтверждений.

Хорошим пользовательским примером такой multi-chain логики является операция обмен usdt: перед выполнением действия важен не только сам актив, но и выбранная blockchain-сеть. На уровне backend такое различие должно сохраняться на всех этапах - от создания операции до мониторинга ее состояния.

Не смешивайте chain и asset

Лучше явно разделять сущности.

class Chain:
    id
    name
    native_asset
    rpc_endpoint
    explorer_url
    confirmation_policy


class Asset:
    id
    symbol
    chain_id
    contract_address
    decimals

Тогда USDT в Ethereum и USDT в TRON будут двумя разными asset records.

Например:

USDT_ETHEREUM
USDT_TRON

Это сильно упрощает:

  • валидацию адресов;
  • выбор RPC;
  • расчет комиссии;
  • поиск транзакции;
  • формирование explorer URL;
  • работу background workers.

Жизненный цикл blockchain-транзакции

Обычный HTTP request заканчивается получением ответа. Blockchain-транзакция может находиться в промежуточном состоянии несколько минут или значительно дольше.

Поэтому ее удобно моделировать через понятные состояния:

created
signed
broadcasted
pending
confirmed
failed
dropped
replaced
expired

После broadcast нельзя сразу считать операцию выполненной. Полученный transaction hash означает лишь то, что транзакция была отправлена в сеть или RPC принял запрос.

Сохраняйте transaction hash сразу

После успешного broadcast идентификатор транзакции нужно записать до выполнения дальнейших действий.

tx_hash = blockchain.broadcast(raw_tx)

transaction.tx_hash = tx_hash
transaction.status = "broadcasted"
transaction.save()

Если worker после этого завершится аварийно, другой процесс сможет продолжить отслеживание уже существующей транзакции.

Нельзя строить логику так:

broadcast()
wait_for_confirmation()
save()

При сбое до save() приложение потеряет связь между внутренней операцией и blockchain-транзакцией.

Не повторяйте broadcast вслепую

Retries особенно опасны в системах, где операция имеет финансовый эффект.

Представим ситуацию: worker отправил транзакцию, RPC ее принял, но соединение оборвалось до получения нормального ответа. Backend считает операцию неудачной и делает retry.

В этом случае возможна повторная отправка.

Поэтому простая конструкция:

try:
    send()
except Timeout:
    retry()

может быть недостаточной.

Перед повтором нужно проверить состояние операции по доступным идентификаторам, nonce или transaction hash, если он был получен.

Идемпотентность на уровне API

Полезно присваивать каждой пользовательской операции уникальный idempotency key.

POST /api/transactions

Idempotency-Key:
2f274d62-b967-4b8f-b38f-a9e3959f9426

Backend сначала проверяет существование операции:

operation = Operation.objects.filter(
    idempotency_key=key
).first()

if operation:
    return operation

И только если такой операции еще нет, создает новую.

Это защищает от двойного нажатия кнопки, повторного HTTP-запроса, timeout на клиенте и повторной доставки сообщения через очередь.

Blockchain workers лучше отделять от Web API

Ожидание подтверждений не должно удерживать HTTP worker.

Лучше создавать операцию через API, сохранять ее в базе и передавать дальнейшую обработку background worker.

Например:

create_transaction.delay(operation_id)

После broadcast отдельная задача может периодически проверять статус:

check_transaction.delay(transaction_id)

Таким образом API остается быстрым даже при медленной blockchain-сети.

Что считать подтвержденной транзакцией

Наличие транзакции в одном блоке не всегда означает окончательность операции.

Часто приложение ожидает несколько подтверждений:

confirmations =
    current_block -
    transaction_block +
    1

Например:

tx block:      24587120
current block: 24587124

confirmations: 5

Политику подтверждений стоит хранить отдельно для каждой сети и типа операции.

{
  "chain": "ethereum",
  "required_confirmations": 12
}

Не стоит жестко зашивать одно число для всех blockchain-сетей.

Нужно учитывать reorg

В некоторых blockchain-сетях возможна реорганизация цепочки: блок, в котором приложение уже увидело транзакцию, перестает входить в актуальную canonical chain.

Поэтому приложение не должно воспринимать первое появление транзакции как необратимое событие.

Worker должен продолжать проверять:

  • наличие транзакции;
  • номер блока;
  • число подтверждений;
  • успешность выполнения;
  • соответствие ожидаемому адресу и сумме.

Для критичных операций требуется более консервативный confirmation threshold.

Какие метрики транзакций собирать

Минимальный набор Prometheus metrics:

blockchain_transactions_created_total
blockchain_transactions_broadcast_total
blockchain_transactions_confirmed_total
blockchain_transactions_failed_total
blockchain_transactions_pending
blockchain_transaction_confirmation_seconds
blockchain_transaction_retries_total

Добавляем labels:

{
  chain="ethereum",
  asset="USDT"
}

После этого Grafana может показывать состояние каждой сети отдельно.

Полезные алерты

Самый очевидный alert - рост числа failed transactions:

rate(
  blockchain_transactions_failed_total[5m]
) > 1

Но есть и другие полезные сигналы.

Транзакции слишком долго находятся в pending

blockchain_transaction_pending_age_seconds > 600

RPC сильно отстает

blockchain_rpc_block_lag > 20

Резко выросла latency RPC

histogram_quantile(
  0.95,
  rate(blockchain_rpc_duration_seconds_bucket[5m])
) > 3

Количество RPC errors превышает нормальный уровень

rate(
  blockchain_rpc_errors_total[5m]
) > 5

Threshold нужно подбирать по реальной нагрузке системы.

Логи blockchain-сервиса

Лог вида:

transaction failed

почти бесполезен.

Лучше использовать structured logging:

{
  "event": "transaction_failed",
  "operation_id": "op_84219",
  "chain": "ethereum",
  "asset": "USDT",
  "tx_hash": "0x...",
  "rpc_provider": "primary",
  "error_type": "rpc_timeout",
  "retry": 2
}

Это позволяет искать события сразу по:

  • operation id;
  • transaction hash;
  • сети;
  • токену;
  • RPC provider;
  • типу ошибки.

При этом приватные ключи, seed phrase и другие секреты ни при каких обстоятельствах не должны попадать в лог.

Seed phrase и private keys нельзя логировать даже в debug

В blockchain-системах последствия неправильного логирования особенно серьезны.

Нельзя писать:

logger.debug(private_key)
logger.info(seed_phrase)
logger.error(wallet_credentials)

Следует также внимательно проверять exception objects. SDK иногда включают в диагностический контекст параметры исходного запроса.

Для production полезно добавить автоматическую redaction:

PRIVATE_KEY=[REDACTED]
SEED_PHRASE=[REDACTED]
API_KEY=[REDACTED]

Также стоит настроить secret scanning репозитория и CI/CD.

Отдельные секреты для каждого окружения

Нельзя использовать одинаковые credentials для development и production.

Минимальное разделение:

development
staging
production

У каждого окружения должны быть собственные:

  • RPC API keys;
  • wallet credentials;
  • database credentials;
  • webhook secrets;
  • service accounts.

Если staging credential скомпрометирован, злоумышленник не должен получить доступ к production.

Health endpoint для blockchain-приложения

Удобный endpoint может возвращать:

{
  "status": "ok",
  "chains": {
    "ethereum": {
      "rpc": "ok",
      "block": 24587124,
      "lag": 1
    },
    "tron": {
      "rpc": "ok",
      "block": 78542102,
      "lag": 0
    }
  },
  "queue": {
    "status": "ok",
    "pending": 12
  }
}

Однако такой endpoint не следует делать публичным без необходимости. Он раскрывает внутреннюю архитектуру и используемые сервисы.

Для Kubernetes можно иметь два разных endpoint:

/health/live
/health/ready

Liveness показывает, что процесс не завис, а readiness - что приложение способно выполнять реальные операции.

Не связывайте readiness напрямую с кратковременным RPC timeout

Если pod становится NotReady после единственного timeout внешнего RPC, Kubernetes может удалить его из Service и тем самым усилить проблему.

Лучше учитывать несколько последовательных ошибок.

Например, один failed RPC request можно считать временной деградацией, а переводить сервис в NotReady только после нескольких последовательных ошибок.

Это защищает от постоянного переключения состояния при кратковременных сетевых сбоях.

Как тестировать blockchain-интеграцию

Запуск integration tests исключительно против настоящей blockchain-сети делает CI медленным и нестабильным.

Тесты лучше разделить.

Unit tests

RPC полностью mock-ируется:

rpc.get_transaction.return_value = {
    "status": "confirmed",
    "block": 123456
}

Integration tests

Используется локальная нода, emulator или тестовое окружение.

Smoke tests

Периодически выполняются реальные read-only запросы к RPC:

get block height
get network id
get known transaction

Так можно быстро понять, что интеграция с провайдером не сломалась после изменения API.

Failover нужно тестировать искусственно

Наличие резервного RPC в конфигурации еще не означает, что переключение действительно работает.

Нужно отдельно проверять сценарии:

  • основной RPC перестал отвечать;
  • RPC начал возвращать HTTP 429;
  • endpoint отвечает, но отстает на сотни блоков;
  • провайдер отвечает слишком медленно;
  • отдельный RPC method начал возвращать ошибку.

Для каждого сценария должно быть понятно, что именно делает приложение: переключается на резервный endpoint, снижает частоту запросов или временно помечает интеграцию как degraded.

Очередь транзакций тоже необходимо мониторить

Иногда blockchain работает нормально, RPC отвечает быстро, но операции пользователей задерживаются из-за переполненной внутренней очереди.

Поэтому собирайте:

queue_depth
oldest_task_age
worker_count
task_processing_seconds
task_failures_total

Например:

blockchain_queue_oldest_task_age_seconds > 120

может быть гораздо полезнее обычной проверки CPU worker.

Correlation ID от API до blockchain

Для диагностики важно иметь возможность проследить одну операцию через всю систему.

Поэтому всем связанным запросам, background tasks и логам стоит передавать один идентификатор операции:

operation_id=84219

Вместе с ним можно хранить transaction hash и имя сети.

Тогда инженер сможет найти полный lifecycle операции в Loki или Elasticsearch без ручного сопоставления десятков записей.

Чек-лист перед запуском blockchain-сервиса в production

  • Для критичных blockchain-сетей настроено несколько RPC endpoints.
  • Проверяется не только HTTP-доступность RPC, но и актуальность блока.
  • Токен идентифицируется вместе с сетью.
  • У транзакции есть явные состояния обработки.
  • Transaction hash сохраняется сразу после broadcast.
  • Повторные задачи идемпотентны.
  • API поддерживает idempotency key для критичных операций.
  • Количество подтверждений настраивается отдельно для каждой сети.
  • Background workers не блокируют HTTP request.
  • Контролируется возраст pending-транзакций.
  • RPC errors и latency отправляются в Prometheus.
  • Есть alert на block lag.
  • В логах присутствует operation id и transaction hash.
  • Private keys и seed phrase никогда не логируются.
  • Секреты development, staging и production разделены.
  • Failover проверяется искусственными отказами.

Итог

Blockchain-приложение с точки зрения эксплуатации - это распределенная система с внешними RPC-зависимостями, асинхронными транзакциями и состоянием, которое нельзя определить по одному HTTP-ответу.

Для надежной работы недостаточно поставить обычные CPU и RAM alerts. Нужно контролировать высоту блоков, состояние RPC, pending-транзакции, количество подтверждений, очередь фоновых задач и каждый этап прохождения пользовательской операции.

При работе сразу с несколькими сетями особенно важно рассматривать blockchain как часть идентичности актива и хранить состояние каждой транзакции независимо. Failover, idempotency, retries и наблюдаемость здесь нужны не меньше, чем в обычной микросервисной архитектуре.

Если эти механизмы проектируются с самого начала, сбой отдельного RPC-провайдера или временное замедление blockchain-сети перестает превращаться в загадочное падение всего приложения: инфраструктура способна определить источник проблемы, переключиться на резервный сервис и сохранить состояние незавершенных операций.

Поделиться:
Сохранить гайд? В закладки браузера