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

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску DeFi-протоколів, NFT-маркетплейсів та криптобірж. Аудит безпеки, токеноміка, інтеграція з наявною інфраструктурою.
Показано 1 з 1Усі 1306 послуг
Розробка Node-as-a-Service платформи
Складний
від 2 тижнів до 3 місяців
Часті запитання

Напрямки блокчейн-розробки

Етапи блокчейн-розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1285
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1198
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    902
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1120
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    588
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    854

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

Запустити блокчейн-ноду вручну — просто. Запустити їх сотнями з гарантією uptime, версіонуванням, ізоляцією клієнтів, біллінгом та API-прокси — це повнофункціональний інфраструктурний продукт. Саме таку платформу будують ті, хто хоче конкурувати з Infura, Alchemy, QuickNode або пропонувати керовану інфраструктуру enterprise-клієнтам у конкретному регіоні або екосистемі.

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

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

Рівень оркестрації нод

Kubernetes — стандарт для управління життєвим циклом нод. Але стандартний 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 — альтернатива, але втрачається ізоляція.

Bootstrap: проблема "першого дня"

Синхронізація Ethereum mainnet з нуля (snap sync): 12–24 години. Archive node: 2–5 тижнів. Неприйнятно для платформи, де клієнт платить з першої хвилини.

Рішення:

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

#!/bin/bash
# Bootstrap нода з снапшоту
NODE_ID=$1
CHAIN=$2
SNAPSHOT_BASE="s3://your-snapshots/${CHAIN}/latest"

# Завантажуємо снапшот
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 — для деяких цепей спільнота підтримує публічні снапшоти (Erigon завантажує снапшоти в BitTorrent/IPFS).

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

Клієнт отримує один endpoint. За ним — load balancer, health checks, rate limiting, управління API ключами.

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. Потрібне кешування відповідей для частих запитів (eth_chainId, eth_blockNumber)
// Приклад RPC прокси з логікою маршрутизації
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) {
    // Перевіряємо кеш
    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())
    }
    
    // Біллінг: записуємо використання
    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 за кожен повернений log

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 метрики для 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}        # біллінгові дані

Правила алертів для 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 швидший час блоку (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× менше часу), краще використання ресурсів. Для нових деплоїв — перший вибір для Ethereum full nodes.

Етапи розробки

Фаза 1 — Core infrastructure (4–6 тижнів): K8s setup, StatefulSet шаблони для 2–3 цепей, pipeline bootstrap зі снапшотів, базовий health checker.

Фаза 2 — API Gateway (3–4 тижні): RPC прокси, управління API ключами, rate limiting, підрахунок compute units.

Фаза 3 — Multi-tenancy & billing (3–4 тижні): ізоляція тенантів, відстеження використання, інтеграція біллінгу (Stripe), дашборд використання.

Фаза 4 — Observability (2–3 тижні): Prometheus + Grafana, алертинг, агрегація логів (Loki), on-call runbooks.

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

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