Разработка Node-as-a-Service платформы

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка Node-as-a-Service платформы
Сложная
от 2 недель до 3 месяцев
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1221
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1163
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    855
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1056
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    828

Разработка Node-as-a-Service платформы

Запускать блокчейн-ноду руками — несложно. Запускать их сотнями, с гарантией uptime, версионированием, изоляцией клиентов, биллингом и API-прокси — это полноценный инфраструктурный продукт. Именно такую платформу строят те, кто хочет конкурировать с Infura, Alchemy, QuickNode или предлагать managed infrastructure для enterprise-клиентов в конкретном регионе или для конкретной экосистемы.

Прежде чем идти в разработку, стоит честно ответить на вопрос: вы строите NaaS для публичных блокчейнов (Ethereum, Solana, BNB) или для частных/permissioned сетей (Hyperledger Besu, Quorum)? Архитектурно это разные системы с разными проблемами.

Уровни архитектуры NaaS платформы

Уровень оркестрации нод

Kubernetes — стандарт для управления lifecycle нод. Но стандартный K8s deployment не подходит для блокчейн-нод напрямую: ноды имеют огромные stateful данные, требуют специфичных network policies, и перезапуск pod = повторная синхронизация с нуля (что занимает дни).

StatefulSet с PVC:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: ethereum-geth
spec:
  serviceName: "geth"
  replicas: 1
  selector:
    matchLabels:
      app: ethereum-geth
  template:
    spec:
      containers:
      - name: geth
        image: ethereum/client-go:v1.13.14
        args:
          - "--datadir=/data"
          - "--http"
          - "--http.addr=0.0.0.0"
          - "--http.vhosts=*"
          - "--http.api=eth,net,web3,txpool"
          - "--ws"
          - "--ws.addr=0.0.0.0"
          - "--maxpeers=50"
          - "--cache=4096"
        ports:
        - containerPort: 8545  # HTTP RPC
        - containerPort: 8546  # WebSocket
        - containerPort: 30303 # P2P
          protocol: TCP
        - containerPort: 30303
          protocol: UDP
        volumeMounts:
        - name: data
          mountPath: /data
        resources:
          requests:
            memory: "16Gi"
            cpu: "4"
          limits:
            memory: "32Gi"
            cpu: "8"
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: "fast-nvme"
      resources:
        requests:
          storage: 3Ti  # Ethereum archive node

Проблема P2P портов в K8s: блокчейн-ноды требуют фиксированных портов для peer discovery (30303/TCP+UDP для Ethereum). NodePort или LoadBalancer с фиксированным портом на каждую ноду — единственный рабочий подход. HostNetwork — альтернатива, но теряется изоляция.

Bootstrapping: проблема "дня первого"

Синхронизация Ethereum mainnet с нуля (snap sync): 12–24 часа. Archive node: 2–5 недель. Это неприемлемо для платформы, где клиент платит с первой минуты.

Решения:

Snapshot distribution — инициализация ноды из актуального snapshot базы данных. Нужно хранить актуальные snapshots (~500GB–3TB в зависимости от типа) и предоставлять HTTP/S3-доступ для bootstrap. Стратегия: snapshot каждые 7 дней, инкрементальные дифы ежедневно.

#!/bin/bash
# Bootstrap нода из snapshot
NODE_ID=$1
CHAIN=$2
SNAPSHOT_BASE="s3://your-snapshots/${CHAIN}/latest"

# Скачиваем snapshot
aws s3 sync ${SNAPSHOT_BASE} /data/${NODE_ID}/chaindata \
  --no-sign-request \
  --region eu-west-1

# Проверяем целостность
sha256sum -c /data/${NODE_ID}/chaindata/CHECKSUM

# Запускаем ноду
kubectl rollout restart statefulset/${CHAIN}-node-${NODE_ID}

Firehose / Erigon snapshots — для некоторых цепей сообщество поддерживает публичные snapshots (Erigon uploads snapshots to BitTorrent/IPFS).

API Gateway и маршрутизация

Клиент получает один endpoint. За ним — load balancer, health checks, rate limiting, API key management.

Client → API Gateway (Kong/custom) → Node Pool → Blockchain Node
                ↓
          [Auth, RateLimit, Billing, Logging]

Кастомный RPC прокси необходим потому что:

  1. Нужно фильтровать опасные методы (debug_traceTransaction — дорогостоящий, только для premium)
  2. Нужна маршрутизация по типу запроса (archive requests → archive node, latest block → sync node)
  3. Нужен response caching для частых запросов (eth_chainId, eth_blockNumber)
// Пример RPC прокси с routing logic
package proxy

type RPCRouter struct {
    archivePool   NodePool
    fullNodePool  NodePool
    cacheClient   *redis.Client
}

var archiveMethods = map[string]bool{
    "eth_getBalance":      true,  // с block parameter != "latest"
    "eth_call":            true,
    "eth_getStorageAt":    true,
    "trace_call":          true,
    "trace_replayTransaction": true,
}

func (r *RPCRouter) Route(req *RPCRequest) NodePool {
    if archiveMethods[req.Method] {
        if req.RequiresHistoricalBlock() {
            return r.archivePool
        }
    }
    return r.fullNodePool
}

func (r *RPCRouter) Handle(w http.ResponseWriter, req *RPCRequest, apiKey string) {
    // Check cache
    cacheKey := req.CacheKey()
    if cached, err := r.cacheClient.Get(ctx, cacheKey).Bytes(); err == nil {
        w.Write(cached)
        return
    }
    
    pool := r.Route(req)
    node := pool.GetHealthyNode()
    resp := node.Forward(req)
    
    if req.IsCacheable() {
        r.cacheClient.Set(ctx, cacheKey, resp, req.CacheTTL())
    }
    
    // Billing: записываем использование
    r.billing.RecordRequest(apiKey, req.Method, resp.ComputeUnits())
    
    w.Write(resp)
}

Health checking и failover

Блокчейн-нода может быть технически "живой" (отвечает на ping), но фактически бесполезной (отстала от сети на 100 блоков, или sync mode = syncing).

type NodeHealthChecker struct {
    client *ethclient.Client
}

func (h *NodeHealthChecker) IsHealthy(ctx context.Context) (bool, error) {
    // Проверяем, не в режиме синхронизации
    syncing, err := h.client.SyncProgress(ctx)
    if err != nil {
        return false, err
    }
    if syncing != nil {
        return false, fmt.Errorf("node is syncing: %d/%d", 
            syncing.CurrentBlock, syncing.HighestBlock)
    }
    
    // Проверяем свежесть блока
    header, err := h.client.HeaderByNumber(ctx, nil)
    if err != nil {
        return false, err
    }
    
    blockAge := time.Since(time.Unix(int64(header.Time), 0))
    if blockAge > 2*time.Minute {
        return false, fmt.Errorf("block too old: %v", blockAge)
    }
    
    return true, nil
}

Health checks нужно запускать каждые 10–30 секунд. Нода исключается из пула при двух последовательных неудачах, возвращается после трёх успешных.

Мультитенантность и изоляция

Разделение ресурсов

Три модели:

Shared nodes — несколько клиентов используют одну ноду. Дёшево, но нет гарантий производительности. Подходит для free tier и небольших проектов.

Dedicated nodes — одна нода на клиента. Гарантированные ресурсы, изоляция. Для enterprise.

Node clusters — несколько реплик за load balancer для одного клиента. Для high-availability требований.

-- Схема биллинга
CREATE TABLE api_keys (
    id UUID PRIMARY KEY,
    customer_id UUID NOT NULL,
    key_hash BYTEA NOT NULL,  -- никогда не храним ключ в открытом виде
    tier VARCHAR(20) NOT NULL,  -- free, starter, pro, enterprise
    rate_limit_rps INTEGER NOT NULL,
    monthly_cu_limit BIGINT,   -- compute units
    node_type VARCHAR(20) NOT NULL,  -- shared, dedicated
    created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE usage_records (
    id BIGSERIAL PRIMARY KEY,
    api_key_id UUID NOT NULL REFERENCES api_keys(id),
    method VARCHAR(100) NOT NULL,
    chain_id INTEGER NOT NULL,
    compute_units INTEGER NOT NULL,
    response_time_ms INTEGER,
    recorded_at TIMESTAMPTZ DEFAULT NOW()
);

-- Индекс для биллинга по периоду
CREATE INDEX idx_usage_billing ON usage_records (api_key_id, recorded_at);

Compute Units (CU) — стандартная единица биллинга в NaaS. Каждый RPC метод имеет вес:

  • eth_blockNumber → 10 CU
  • eth_getTransactionReceipt → 15 CU
  • eth_call → 26 CU
  • trace_replayTransaction → 75 CU
  • eth_getLogs → 75 CU + 1 CU per log returned

Rate limiting

Redis-based sliding window лучше token bucket для RPC нагрузок:

func (rl *RateLimiter) Allow(ctx context.Context, apiKey string, rps int) (bool, error) {
    now := time.Now().UnixMilli()
    window := int64(1000)  // 1 секунда в миллисекундах
    
    pipe := rl.redis.Pipeline()
    pipe.ZRemRangeByScore(ctx, apiKey, "0", 
        strconv.FormatInt(now-window, 10))
    pipe.ZCard(ctx, apiKey)
    pipe.ZAdd(ctx, apiKey, redis.Z{Score: float64(now), Member: now})
    pipe.Expire(ctx, apiKey, 2*time.Second)
    
    results, err := pipe.Exec(ctx)
    count := results[1].(*redis.IntCmd).Val()
    
    return count < int64(rps), nil
}

Мониторинг и алертинг

Метрики которые важны

# Prometheus metrics для NaaS
- node_sync_lag_blocks{chain, node_id}          # отставание от head
- node_peer_count{chain, node_id}               # число пиров
- rpc_request_duration_seconds{method, status}  # p50, p95, p99
- rpc_requests_total{method, chain, tier}       # для биллинга
- node_restart_total{chain, node_id, reason}    # частота рестартов
- compute_units_consumed{api_key, chain}        # биллинговые данные

Alert rules для on-call:

Метрика Порог Severity
sync_lag_blocks > 10 блоков Warning
sync_lag_blocks > 50 блоков Critical
peer_count < 5 Warning
rpc_error_rate > 5% за 5 мин Warning
node_restart_total > 3 за час Critical

Поддерживаемые клиенты и их специфика

Цепь Клиент Размер данных Особенности
Ethereum (full) Geth / Reth ~1.2 TB snap sync доступен
Ethereum (archive) Erigon ~2.5 TB trace API отличается от Geth
Solana Agave (Solana Labs) ~50 TB (full ledger) geyser plugin для стриминга
BNB Chain BSC Geth fork ~800 GB faster block time (3s)
Polygon Bor + Heimdall ~600 GB два процесса на одну ноду
Arbitrum Nitro ~1 TB sequencer feed, не P2P
Base op-geth ~800 GB OP Stack, op-node рядом

Reth — новый Ethereum-клиент на Rust от Paradigm. Значительно быстрее Geth при синхронизации (~2× меньше времени), лучше resource utilization. Для новых деплоев — первый выбор для Ethereum full nodes.

Этапы разработки

Фаза 1 — Core infrastructure (4–6 нед): K8s setup, StatefulSet templates для 2–3 цепей, snapshot bootstrap pipeline, базовый health checker.

Фаза 2 — API Gateway (3–4 нед): RPC прокси, API key management, rate limiting, compute units counting.

Фаза 3 — Multi-tenancy & billing (3–4 нед): tenant isolation, usage tracking, billing integration (Stripe), usage dashboard.

Фаза 4 — Observability (2–3 нед): Prometheus + Grafana, alerting, log aggregation (Loki), on-call runbooks.

Фаза 5 — Self-service portal (4–6 нед): веб-интерфейс для создания нод, просмотра метрик, управления API ключами.

Итого: 16–23 недели до production-ready платформы. Добавление каждой новой цепи после запуска — 1–2 недели (шаблон + snapshot pipeline + тестирование).