Files
vsecoder 7e7393e4f8 chore: initial commit for v0.0.1
DChain single-node blockchain + React Native messenger client.

Core:
- PBFT consensus with multi-sig validator admission + equivocation slashing
- BadgerDB + schema migration scaffold (CurrentSchemaVersion=0)
- libp2p gossipsub (tx/v1, blocks/v1, relay/v1, version/v1)
- Native Go contracts (username_registry) alongside WASM (wazero)
- WebSocket gateway with topic-based fanout + Ed25519-nonce auth
- Relay mailbox with NaCl envelope encryption (X25519 + Ed25519)
- Prometheus /metrics, per-IP rate limit, body-size cap

Deployment:
- Single-node compose (deploy/single/) with Caddy TLS + optional Prometheus
- 3-node dev compose (docker-compose.yml) with mocked internet topology
- 3-validator prod compose (deploy/prod/) for federation
- Auto-update from Gitea via /api/update-check + systemd timer
- Build-time version injection (ldflags → node --version)
- UI / Swagger toggle flags (DCHAIN_DISABLE_UI, DCHAIN_DISABLE_SWAGGER)

Client (client-app/):
- Expo / React Native / NativeWind
- E2E NaCl encryption, typing indicator, contact requests
- Auto-discovery of canonical contracts, chain_id aware, WS reconnect on node switch

Documentation:
- README.md, CHANGELOG.md, CONTEXT.md
- deploy/single/README.md with 6 operator scenarios
- deploy/UPDATE_STRATEGY.md with 4-layer forward-compat design
- docs/contracts/*.md per contract
2026-04-17 14:16:44 +03:00
..
2026-04-17 14:16:44 +03:00
2026-04-17 14:16:44 +03:00
2026-04-17 14:16:44 +03:00
2026-04-17 14:16:44 +03:00

DChain single-node deployment

Один узел + опционально Caddy TLS + опционально Prometheus/Grafana. Подходит под четыре основных сценария:

  1. личная нода — публичная или приватная с токеном,
  2. первый узел новой сети (genesis),
  3. присоединение к существующей сети (relay / observer / validator),
  4. headless API-нода для мобильных клиентов — без HTML-UI.

Для 3-валидаторного кластера смотри ../prod/.


Навигация


0. Что поднимается

Базовый compose (docker compose up -d) поднимает:

Сервис Что это Порты
node сама нода DChain (dchain-node-slim image) 4001 (libp2p P2P, наружу), 8080 (HTTP/WS — только через Caddy)
caddy TLS edge с auto-HTTPS (Let's Encrypt) 80, 443, 443/udp

С --profile monitor добавляются:

Сервис Что это Порты
prometheus метрики + TSDB (30 дней retention) внутри сети
grafana дашборды 3000

1. Быстрый старт

# 1. Сгенерируй ключ ноды (один раз, храни в безопасности).
docker build -t dchain-node-slim -f ../prod/Dockerfile.slim ../..
mkdir -p keys
docker run --rm --entrypoint /usr/local/bin/client \
  -v "$PWD/keys:/out" dchain-node-slim \
  keygen --out /out/node.json

# 2. Скопируй env и отредактируй.
cp node.env.example node.env
$EDITOR node.env       # минимум: DCHAIN_ANNOUNCE, DOMAIN, DCHAIN_API_TOKEN

# 3. Подними.
docker compose up -d

# 4. (опционально) Мониторинг.
GRAFANA_ADMIN_PW=$(openssl rand -hex 16) \
  docker compose --profile monitor up -d
# → Grafana http://<host>:3000, источник http://prometheus:9090

# 5. Проверь живость.
curl -s https://$DOMAIN/api/netstats
curl -s https://$DOMAIN/api/well-known-version

Windows: если запускаете через Docker Desktop и Git Bash, добавляйте MSYS_NO_PATHCONV=1 перед командами с /out, /keys и подобными Unix-путями — иначе Git Bash сконвертирует их в Windows-пути.


2. Сценарии конфигурации

Все сценарии отличаются только содержимым node.env. Пересоздавать контейнер: docker compose up -d --force-recreate node.

2.1. Публичная нода с UI и открытым Swagger

Когда подходит: вы хотите показать Explorer всем (адрес для поиска по pubkey, история блоков, список валидаторов), и оставить Swagger как живую документацию API.

# node.env
DCHAIN_ANNOUNCE=/ip4/203.0.113.10/tcp/4001
DOMAIN=dchain.example.com
ACME_EMAIL=you@example.com

# никакого токена — публичный режим
# UI и Swagger зажгутся по умолчанию (флаги ниже не задаём)

Результат:

URL Что там
https://$DOMAIN/ Блок-эксплорер (главная)
https://$DOMAIN/address?pub=… Баланс + история по pubkey
https://$DOMAIN/tx?id=… Детали транзакции
https://$DOMAIN/validators Список валидаторов
https://$DOMAIN/tokens Зарегистрированные токены
https://$DOMAIN/swagger Swagger UI — интерактивная OpenAPI спека
https://$DOMAIN/swagger/openapi.json Сырой OpenAPI JSON — для codegen
https://$DOMAIN/api/* Вся JSON-API поверхность
https://$DOMAIN/metrics Prometheus exposition

2.2. Headless API-нода (без UI, Swagger открыт)

Когда подходит: нода — это бэкенд для мобильного приложения, HTML-эксплорер не нужен, но Swagger хочется оставить как доку для разработчиков.

# node.env
DCHAIN_ANNOUNCE=/ip4/203.0.113.20/tcp/4001
DOMAIN=api.dchain.example.com

# Отключаем HTML-страницы эксплорера, но НЕ Swagger.
DCHAIN_DISABLE_UI=true

Эффект:

  • GET /404 page not found
  • GET /address, /tx, /validators, /tokens, /contract и все /assets/explorer/* → 404.
  • GET /swagger → Swagger UI, работает (без изменений).
  • GET /api/*, GET /metrics, GET /api/ws → работают.

Нода логирует:

[NODE] explorer UI: disabled (--disable-ui)
[NODE] swagger:     http://0.0.0.0:8080/swagger

2.3. Полностью приватная (токен на всё, UI выключен)

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

# node.env
DCHAIN_ANNOUNCE=/ip4/203.0.113.30/tcp/4001
DOMAIN=node.personal.example

DCHAIN_API_TOKEN=$(openssl rand -hex 32)   # скопируйте в клиент
DCHAIN_API_PRIVATE=true                     # закрывает и read-эндпоинты

# UI вам не нужен, а кому бы и был — всё равно 401 без токена.
DCHAIN_DISABLE_UI=true

Эффект:

  • Любой /api/* без Authorization: Bearer <token>401.
  • /swagger по-прежнему отдаётся (он не кастомизируется под токены, а API-вызовы из Swagger UI будут возвращать 401 — это нормально).
  • P2P порт 4001 остаётся открытым — без него нода не синкается с сетью.

Передать токен клиенту:

// client-app/lib/api.ts — в post()/get() добавить:
headers: { 'Authorization': 'Bearer ' + YOUR_TOKEN }

// для WebSocket — токен как query-параметр:
this.url = base.replace(/^http/, 'ws') + '/api/ws?token=' + YOUR_TOKEN;

2.4. Только-API без Swagger

Когда подходит: максимально hardened headless-нода. Даже описание API поверхности не должно быть на виду.

DCHAIN_ANNOUNCE=/ip4/203.0.113.40/tcp/4001
DOMAIN=rpc.dchain.example.com

DCHAIN_DISABLE_UI=true
DCHAIN_DISABLE_SWAGGER=true

Эффект:

  • / → 404, /swagger → 404, /api/* → работает.
  • В логах:
    [NODE] explorer UI: disabled (--disable-ui)
    [NODE] swagger:     disabled (--disable-swagger)
    
  • Swagger спеку всё равно можно сгенерить локально: go run ./cmd/node в dev-режиме → http://localhost:8080/swagger/openapi.json → сохранить.

2.5. Первая нода новой сети (--genesis)

Любой из сценариев выше + установить DCHAIN_GENESIS=true при самом первом запуске. Нода создаст блок 0 со своим же pubkey как единственным валидатором. После первого успешного старта удалите эту строку из node.env (no-op, но шумит в логах).

DCHAIN_GENESIS=true
DCHAIN_ANNOUNCE=/ip4/203.0.113.10/tcp/4001
DOMAIN=dchain.example.com

Проверка:

curl -s https://$DOMAIN/api/netstats | jq .validator_count   # → 1
curl -s https://$DOMAIN/api/network-info | jq .genesis_hash   # сохраните

2.6. Присоединение к существующей сети (--join)

Любой из сценариев + DCHAIN_JOIN со списком HTTP URL-ов seed-нод. Нода подтянет chain_id, genesis hash, список валидаторов и пиров автоматически через /api/network-info. Запускается как observer по умолчанию — применяет блоки и принимает tx, но не голосует.

DCHAIN_JOIN=https://seed1.dchain.example.com,https://seed2.dchain.example.com
DCHAIN_ANNOUNCE=/ip4/203.0.113.50/tcp/4001
DOMAIN=node2.example.com

Чтобы стать валидатором — существующий валидатор должен подать ADD_VALIDATOR с мульти-подписями. См. ../prod/README.md → "Add a 4th validator".


3. HTTP-поверхность

Что отдаёт нода по умолчанию (все /api/* всегда включены, даже с DCHAIN_DISABLE_UI=true):

Публичные health / discovery

Endpoint Назначение
/api/netstats tip height, total tx count, supply, validator count
/api/network-info one-shot bootstrap payload для нового клиента/ноды
/api/well-known-version node_version, protocol_version, features[], build{tag, commit, date, dirty}
/api/well-known-contracts канонические contract_id → name map
/api/update-check сравнивает свой commit с Gitea release (нужен DCHAIN_UPDATE_SOURCE_URL)
/api/validators активный validator set
/api/peers живые libp2p пиры + их версия (из gossip-топика dchain/version/v1)

Chain explorer JSON

Endpoint Назначение
/api/blocks?limit=N последние N блоков
/api/block/{index} один блок
/api/txs/recent?limit=N последние N tx
/api/tx/{id} одна транзакция
/api/address/{pubkey_or_DC-addr} баланс + история
/api/identity/{pubkey_or_DC-addr} ed25519 ↔ x25519 binding
/api/relays зарегистрированные relay-ноды
/api/contracts / /api/contracts/{id} / /api/contracts/{id}/state/{key} контракты
/api/tokens / /api/tokens/{id} / /api/nfts токены и NFT
/api/channels/{id} / /api/channels/{id}/members каналы и члены (для fan-out)

Submit / Real-time

Endpoint Назначение
POST /api/tx submit подписанной tx (rate-limit + body-cap; token-gate если задан)
GET /api/ws WebSocket (auth, topic subscribe, submit_tx, typing)
GET /api/events SSE (односторонний legacy stream)

HTML (выключается DCHAIN_DISABLE_UI=true)

Endpoint Назначение
/ главная эксплорера
/address, /tx, /node, /relays, /validators, /contract, /tokens, /token страницы
`/assets/explorer/*.js css`

Swagger (выключается DCHAIN_DISABLE_SWAGGER=true)

Endpoint Назначение
/swagger Swagger UI (грузит swagger-ui-dist с unpkg)
/swagger/openapi.json сырая OpenAPI 3.0 спека

Prometheus

Endpoint Назначение
/metrics exposition, всегда включён

Защита /metrics: у эндпоинта нет встроенной авторизации. В публичной деплое закройте его на уровне Caddy — пример в Caddyfile: рестрикт по IP/токену scrape-сервера.


4. Auto-update от Gitea

После поднятия проекта на Gitea:

# node.env
DCHAIN_UPDATE_SOURCE_URL=https://gitea.example.com/api/v1/repos/dchain/dchain/releases/latest
DCHAIN_UPDATE_SOURCE_TOKEN=              # опционально, для приватных repo
UPDATE_ALLOW_MAJOR=false                  # блокирует v1.x → v2.y без явного согласия

Проверка:

curl -s https://$DOMAIN/api/update-check | jq .
# {
#   "current":         { "tag": "v0.5.0", "commit": "abc1234", ... },
#   "latest":          { "tag": "v0.5.1", "url": "https://gitea...", ... },
#   "update_available": true,
#   "checked_at":      "2026-04-17T10:41:03Z"
# }

systemd-таймер для бесшумного hourly-обновления:

sudo cp systemd/dchain-update.{service,timer} /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now dchain-update.timer

Скрипт update.sh:

  1. спрашивает /api/update-check — если update_available: false, выходит;
  2. делает git fetch --tags, checkout на новый тег;
  3. semver-guard: блокирует major-скачок (vN.x → vN+1.y) если UPDATE_ALLOW_MAJOR != true;
  4. ребилдит образ с injected версией (VERSION_TAG/COMMIT/DATE/DIRTY);
  5. smoke-test node --version;
  6. docker compose up -d --force-recreate node;
  7. polling /api/netstats — до 60 сек, fail loud если не ожил.

Подробнее — ../UPDATE_STRATEGY.md.


5. Обновление / бэкап / восстановление

# Ручное обновление (downtime ~5-8 сек):
docker compose pull
docker compose build
docker compose up -d --force-recreate node

# Проверить что новая версия поднялась:
docker exec dchain_node /usr/local/bin/node --version
curl -s https://$DOMAIN/api/well-known-version | jq .build

# Backup chain state:
docker run --rm -v dchain-single_node_data:/data -v "$PWD":/bak alpine \
  tar czf /bak/dchain-$(date +%F).tar.gz -C /data .

# Восстановление:
docker compose stop node
docker run --rm -v dchain-single_node_data:/data -v "$PWD":/bak alpine \
  sh -c "rm -rf /data/* && tar xzf /bak/dchain-2026-04-10.tar.gz -C /data"
docker compose up -d node

6. Troubleshooting

Симптом Проверка
failed to get certificate в Caddy DNS A-record на DOMAIN → этот хост? Порт 80 открыт?
/api/tx возвращает 401 Токен в заголовке совпадает с DCHAIN_API_TOKEN?
Ноды не видят друг друга Порт 4001 открыт? DCHAIN_ANNOUNCE = публичный IP?
Блоки не растут (validator mode) `docker compose logs node
/ возвращает 404 DCHAIN_DISABLE_UI=true установлен — либо уберите, либо используйте /api/*
/swagger возвращает 404 DCHAIN_DISABLE_SWAGGER=true — уберите, либо хостьте openapi.json отдельно
update-check возвращает 503 DCHAIN_UPDATE_SOURCE_URL не задан или пустой
update-check возвращает 502 Gitea недоступна или URL неверный — проверьте curl $DCHAIN_UPDATE_SOURCE_URL руками
FATAL: genesis hash mismatch В volume чейн с другим genesis. docker volume rm dchain-single_node_dataup -d (потеря локальных данных)
Диск растёт BadgerDB GC работает раз в 5 мин; для блокчейна с десятками тысяч блоков обычно < 500 MB
--version выдаёт dev Образ собран без --build-arg VERSION_* — ребилдните через update.sh или docker build --build-arg VERSION_TAG=... вручную