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
311 lines
13 KiB
Markdown
311 lines
13 KiB
Markdown
# DChain
|
||
|
||
Блокчейн-стек для децентрализованного мессенджера:
|
||
- **PBFT** консенсус с multi-sig validator governance и equivocation slashing
|
||
- **Native Go контракты** рядом с WASM (wazero) — нулевая задержка для
|
||
системных сервисов типа username registry
|
||
- **WebSocket push API** — клиент не опрашивает, все события прилетают
|
||
на соединение
|
||
- **E2E-шифрованный relay mailbox** на libp2p gossipsub с TTL live-detection
|
||
- **Prometheus `/metrics`**, Caddy auto-HTTPS, observer mode, load-test
|
||
- React Native / Expo мессенджер (`client-app/`)
|
||
|
||
## Содержание
|
||
|
||
- [Быстрый старт (dev)](#быстрый-старт-dev)
|
||
- [Продакшен деплой](#продакшен-деплой)
|
||
- [Клиент](#клиент)
|
||
- [Архитектура](#архитектура)
|
||
- [Контракты](#контракты)
|
||
- [REST / WebSocket API](#rest--websocket-api)
|
||
- [CLI](#cli)
|
||
- [Мониторинг](#мониторинг)
|
||
- [Тесты](#тесты)
|
||
- [История изменений](#история-изменений)
|
||
|
||
---
|
||
|
||
## Быстрый старт (dev)
|
||
|
||
3 валидатора в docker-compose с замоканной «интернет» топологией:
|
||
|
||
```bash
|
||
docker compose up --build -d
|
||
open http://localhost:8081 # Explorer главной ноды
|
||
curl -s http://localhost:8081/api/netstats # синхронность ноды
|
||
```
|
||
|
||
После поднятия нативный `username_registry` уже доступен — отдельный
|
||
`--profile deploy` больше не нужен. Системные контракты регистрируются
|
||
в Go-коде при запуске (см. `blockchain/native.go`).
|
||
|
||
## Продакшен деплой
|
||
|
||
Два варианта, по масштабу:
|
||
|
||
### 🔸 Single-node (`deploy/single/`)
|
||
|
||
**Рекомендуется для личного/первого узла.** Один узел + Caddy TLS + опциональный Prometheus.
|
||
|
||
```bash
|
||
cd deploy/single
|
||
|
||
# 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. Конфиг
|
||
cp node.env.example node.env && $EDITOR node.env
|
||
# минимум: DCHAIN_ANNOUNCE=<public ip>, DOMAIN=<fqdn>, ACME_EMAIL=<your>
|
||
|
||
# 3. Вверх
|
||
docker compose up -d
|
||
|
||
# 4. Проверка
|
||
curl -s https://$DOMAIN/api/netstats
|
||
curl -s https://$DOMAIN/api/well-known-version
|
||
```
|
||
|
||
#### Модели доступа
|
||
|
||
| Режим | `DCHAIN_API_TOKEN` | `DCHAIN_API_PRIVATE` | Поведение |
|
||
|-------|:------------------:|:--------------------:|-----------|
|
||
| Public (default) | не задан | — | Все могут читать и писать |
|
||
| Public reads / token writes | задан | `false` | Читать — любой; submit tx — только с токеном |
|
||
| Fully private | задан | `true` | Всё требует `Authorization: Bearer <token>` |
|
||
|
||
#### UI / Swagger — включать или нет?
|
||
|
||
| Нужно | `DCHAIN_DISABLE_UI` | `DCHAIN_DISABLE_SWAGGER` | Где что открыто |
|
||
|-------|:-------------------:|:------------------------:|-----------------|
|
||
| Публичная с эксплорером + живой docs | не задано | не задано | `/` Explorer, `/swagger` UI, `/api/*`, `/metrics` |
|
||
| Headless API-нода с открытой OpenAPI | `true` | не задано | `/swagger` UI, `/api/*`, `/metrics` (Explorer выкл.) |
|
||
| Личная hardened | `true` | `true` | только `/api/*` + `/metrics` |
|
||
|
||
Флаги читаются и из CLI (`--disable-ui`, `--disable-swagger`), и из env.
|
||
`/api/*` JSON-поверхность регистрируется всегда — отключить её можно
|
||
только на уровне Caddy / firewall.
|
||
|
||
#### Auto-update
|
||
|
||
```ini
|
||
# node.env — когда проект у вас в Gitea
|
||
DCHAIN_UPDATE_SOURCE_URL=https://gitea.example.com/api/v1/repos/OWNER/REPO/releases/latest
|
||
UPDATE_ALLOW_MAJOR=false
|
||
```
|
||
|
||
```bash
|
||
# Hourly systemd timer с 15-мин jitter
|
||
sudo cp deploy/single/systemd/dchain-update.{service,timer} /etc/systemd/system/
|
||
sudo systemctl daemon-reload
|
||
sudo systemctl enable --now dchain-update.timer
|
||
```
|
||
|
||
Скрипт `deploy/single/update.sh` читает `/api/update-check`, делает
|
||
semver-guarded `git checkout tag`, ребилдит образ с injected версией,
|
||
smoke-test `node --version`, recreate, health poll.
|
||
|
||
Подробные сценарии (первая нода, joiner, приватная, headless) +
|
||
полный API reference + systemd-интеграция + troubleshooting — в
|
||
**[`deploy/single/README.md`](deploy/single/README.md)**.
|
||
|
||
### 🔹 Multi-validator (`deploy/prod/`)
|
||
|
||
3 validator'а в PBFT-кворуме, Caddy с ip_hash для WS-стикинесса и
|
||
least-conn для REST. Для федераций / консорциумов — см.
|
||
`deploy/prod/README.md`.
|
||
|
||
## Клиент
|
||
|
||
```bash
|
||
cd client-app
|
||
npm install
|
||
npx expo start # Expo Dev Tools — iOS / Android / Web
|
||
```
|
||
|
||
React Native + Expo + NativeWind. Ключевые экраны:
|
||
|
||
| Экран | Описание |
|
||
|-------|----------|
|
||
| Welcome / Create Account | Генерация Ed25519 + X25519 keypair |
|
||
| Chat List | Диалоги + real-time via WS (`inbox:<x25519>`) |
|
||
| Chat | E2E NaCl, typing-индикатор, day-separators |
|
||
| Contact Requests | Входящие запросы (push через `addr:<pub>`) |
|
||
| Add Contact | Поиск по `@username` (native registry) или hex |
|
||
| Wallet | Баланс, история (push), кошельковые action buttons |
|
||
| Settings | Нода, `@username` покупка, экспорт ключа |
|
||
|
||
Клиент подсоединяется к одной ноде через HTTP + WebSocket:
|
||
- **WS для всего real-time** (balance, inbox, contacts, tx commit).
|
||
- **HTTP fallback** через 15 s после обрыва, автоматически.
|
||
- **Auto-discovery** канонического `username_registry` через
|
||
`/api/well-known-contracts`.
|
||
|
||
## Архитектура
|
||
|
||
```
|
||
┌────────────┐ libp2p ┌────────────┐ libp2p ┌────────────┐
|
||
│ node1 │◄─────pubsub──►│ node2 │◄─────pubsub──►│ node3 │
|
||
│ validator │ │ validator │ │ validator │
|
||
│ + relay │ │ + relay │ │ + relay │
|
||
└─────┬──────┘ └─────┬──────┘ └─────┬──────┘
|
||
│ │ │
|
||
│ HTTPS / wss (via Caddy) │ │
|
||
▼ ▼ ▼
|
||
mobile / web / CLI clients (load-balanced ip_hash for WS)
|
||
```
|
||
|
||
Слои (`blockchain/`):
|
||
- `chain.go` — блочная машина (applyTx, AddBlock, BadgerDB)
|
||
- `native.go` — системные Go-контракты (интерфейс + registry)
|
||
- `native_username.go` — реализация username_registry
|
||
- `equivocation.go` — проверка evidence для SLASH
|
||
- `types.go` — транзакции, payloads
|
||
|
||
Сети (`p2p/`):
|
||
- gossipsub topics `dchain/tx/v1`, `dchain/blocks/v1`
|
||
- стрим-протокол `/dchain/sync/1.0.0` для catch-up
|
||
- mDNS + DHT для peer discovery
|
||
|
||
Консенсус (`consensus/pbft.go`):
|
||
- Pre-prepare → Prepare → Commit, 2/3 quorum
|
||
- liveness tracking, equivocation detection (`recordVote`, `TakeEvidence`)
|
||
- per-sender FIFO мемпул + round-robin drain до `MaxTxsPerBlock`
|
||
|
||
Node service layer (`node/`):
|
||
- HTTP API + SSE + **WebSocket hub** с auth и topic-based fanout
|
||
- Prometheus `/metrics` (zero-dep)
|
||
- event bus (`events.go`) — SSE + WS + future consumers с одного emit'а
|
||
- rate-limiter + body-size cap на tx submit
|
||
|
||
Relay (`relay/`):
|
||
- encrypted envelope routing через gossipsub
|
||
- BadgerDB mailbox для offline-получателей (TTL 7 дней)
|
||
- `SetOnStore` hook push'ит новые envelopes в WS
|
||
|
||
## Контракты
|
||
|
||
**Системные (native Go)** — зарегистрированы при запуске ноды:
|
||
|
||
| ID | Назначение | ABI |
|
||
|----|-----------|-----|
|
||
| `native:username_registry` | `@username` → адрес | `register`, `resolve`, `lookup`, `transfer`, `release` |
|
||
|
||
**Пользовательские (WASM)** — деплоятся через `DEPLOY_CONTRACT` tx.
|
||
TinyGo SDK + host functions (get_state, transfer, get_caller, log,
|
||
call_contract, …). Ручной деплой из `scripts/deploy_contracts.sh` или
|
||
CLI `client deploy-contract --wasm ...`.
|
||
|
||
Обзор ABI / примеры — `docs/contracts/` (отдельные файлы на auction,
|
||
escrow, governance, username_registry).
|
||
|
||
## REST / WebSocket API
|
||
|
||
### Chain
|
||
|
||
| Endpoint | Описание |
|
||
|----------|----------|
|
||
| `GET /api/netstats` | total_blocks, tx_count, supply, peer count |
|
||
| `GET /api/blocks?limit=N` | Последние блоки |
|
||
| `GET /api/block/{index}` | Конкретный блок |
|
||
| `GET /api/tx/{id}` | Транзакция по ID |
|
||
| `GET /api/txs/recent?limit=N` | Последние tx (O(limit)) |
|
||
| `GET /api/address/{pubkey}` | Баланс + история tx |
|
||
| `GET /api/validators` | Активный validator set |
|
||
| `GET /api/network-info` | One-shot bootstrap payload (chain_id, genesis, peers, validators, contracts) |
|
||
| `GET /api/peers` | Живые libp2p peer'ы |
|
||
| `GET /api/well-known-contracts` | Канонические контракты (native + WASM) |
|
||
| `GET /api/contracts/{id}` | Метаданные контракта, `"native": true/false` |
|
||
| `GET /api/contracts/{id}/state/{key}` | Прямое чтение state |
|
||
| `GET /api/relays` | Список relay-нод (filtered TTL) |
|
||
| `POST /api/tx` | Submit signed tx (rate-limited, size-capped) |
|
||
|
||
### Real-time
|
||
|
||
- `GET /api/events` — Server-Sent Events (classic, 1-way)
|
||
- `GET /api/ws` — **WebSocket** (bidirectional, recommended)
|
||
|
||
WS protocol — см. `node/ws.go`:
|
||
|
||
```json
|
||
{ "op": "auth", "pubkey": "...", "sig": "..." }
|
||
{ "op": "subscribe", "topic": "addr:..." | "inbox:..." | "typing:..." | "blocks" | "tx" }
|
||
{ "op": "unsubscribe", "topic": "..." }
|
||
{ "op": "submit_tx", "tx": {...}, "id": "client-req-id" }
|
||
{ "op": "typing", "to": "<x25519>" }
|
||
{ "op": "ping" }
|
||
```
|
||
|
||
Server push:
|
||
|
||
```json
|
||
{ "event": "hello", "chain_id": "...", "auth_nonce": "..." }
|
||
{ "event": "block", "data": {...} }
|
||
{ "event": "tx", "data": {...} }
|
||
{ "event": "inbox", "data": { id, recipient_pub, sender_pub, sent_at } }
|
||
{ "event": "typing", "data": { from, to } }
|
||
{ "event": "submit_ack", "id": "...", "status": "accepted|rejected", "reason": "..." }
|
||
```
|
||
|
||
Scoped топики (`addr:`, `inbox:`, `typing:`) требуют auth. Без auth
|
||
доступны только публичные (`blocks`, `tx`, `contract_log`).
|
||
|
||
## CLI
|
||
|
||
```bash
|
||
client keygen --out key.json
|
||
client balance --key key.json --node URL
|
||
client transfer --key key.json --to <pub> --amount <µT> --node URL
|
||
client call-contract --key key.json --contract native:username_registry \
|
||
--method register --args '["alice"]' --amount 10000 \
|
||
--node URL
|
||
client add-validator --key key.json --target <pub> --cosigs pub:sig,pub:sig
|
||
client admit-sign --key validator.json --target <candidate-pub>
|
||
client remove-validator --key key.json --target <pub>
|
||
# ... полный список — `client` без аргументов
|
||
```
|
||
|
||
Node flags (все читают `DCHAIN_*` env fallbacks):
|
||
- `--db`, `--listen`, `--announce`, `--peers`, `--validators`
|
||
- `--join http://seed1:8080,http://seed2:8080` (multi-seed, persisted)
|
||
- `--genesis`, `--observer`, `--register-relay`
|
||
- `--log-format text|json`, `--allow-genesis-mismatch`
|
||
|
||
## Мониторинг
|
||
|
||
Prometheus endpoint `/metrics` на каждой ноде. Ключевые метрики:
|
||
|
||
```
|
||
dchain_blocks_total # committed blocks count
|
||
dchain_txs_total # tx count
|
||
dchain_tx_submit_accepted_total
|
||
dchain_tx_submit_rejected_total
|
||
dchain_ws_connections # current WS sockets
|
||
dchain_peer_count_live # live libp2p peer count
|
||
dchain_max_missed_blocks # worst validator liveness gap
|
||
dchain_block_commit_seconds # histogram of AddBlock time
|
||
```
|
||
|
||
Grafana dashboard + provisioning — `deploy/prod/grafana/` (create as
|
||
needed; Prometheus data source is auto-wired in compose profile
|
||
`monitor`).
|
||
|
||
## Тесты
|
||
|
||
```bash
|
||
go test ./... # blockchain + consensus + relay + identity + vm
|
||
go run ./cmd/loadtest \
|
||
--node http://localhost:8081 \
|
||
--funder testdata/node1.json \
|
||
--clients 50 --duration 60s # end-to-end WS + submit_tx + native contract path
|
||
```
|
||
|
||
## История изменений
|
||
|
||
Подробный список того, что сделано за последнюю итерацию (стабилизация
|
||
чейна, governance, WS gateway, observability, native contracts,
|
||
deployment) — `CHANGELOG.md`.
|