feat: resource caps, Saved Messages, author walls, docs for node bring-up

Node flags (cmd/node/main.go):
  --max-cpu / --max-ram-mb — Go runtime caps (GOMAXPROCS / GOMEMLIMIT)
  --feed-disk-limit-mb — hard 507 refusal for new post bodies over quota
  --chain-disk-limit-mb — advisory watcher (can't reject blocks without
  breaking consensus; logs WARN every minute)

Client — Saved Messages (self-chat):
  - Auto-created on sign-in, pinned top of chat list, blue bookmark avatar
  - Send short-circuits the relay (no encrypt, no fee, no mailbox hop)
  - Empty state rendered outside inverted FlatList — fixes the mirrored
    "say hi…" on Android RTL-aware layout builds
  - PostCard shows "You" for own posts instead of the self-contact alias

Client — user walls:
  - New route /(app)/feed/author/[pub] with infinite-scroll via
    `created_at` cursor and pull-to-refresh
  - Profile screen gains "View posts" button (universal) next to
    "Open chat" (contact-only)

Feed pipeline:
  - Bump client JPEG quality 0.5 → 0.75 to match server scrubber (Q=75),
    so a 60 KiB compose doesn't balloon past 256 KiB after server re-encode
  - ErrPostTooLarge now wraps with the actual size vs cap, errors.Is
    preserved in the HTTP layer
  - FeedMailbox quota + DiskUsage surface — supports new CLI flag

README:
  - Step-by-step "first node / joiner" section on the landing page,
    full flag tables incl. the new resource-cap group, minimal
    checklists for open/private/low-end deployments
This commit is contained in:
vsecoder
2026-04-19 13:14:47 +03:00
parent e6f3d2bcf8
commit a75cbcd224
18 changed files with 870 additions and 102 deletions

155
README.md
View File

@@ -22,6 +22,7 @@
## Содержание
- [Быстрый старт](#быстрый-старт)
- [Поднятие ноды — пошагово](#поднятие-ноды--пошагово)
- [Продакшен деплой](#продакшен-деплой)
- [Архитектура](#архитектура)
- [REST / WebSocket API](#rest--websocket-api)
@@ -66,6 +67,160 @@ curl -s http://localhost:8080/api/well-known-version | jq .
3-node dev-кластер (для тестов PBFT кворума, slashing, federation): `docker compose up --build -d` — см. [`docs/quickstart.md`](docs/quickstart.md).
## Поднятие ноды — пошагово
Ниже — полный минимум для двух сценариев, которые покрывают 99% случаев:
**первая нода сети** (genesis) и **присоединение к существующей сети**.
Все флаги читаются также из соответствующего `DCHAIN_*` env-var (CLI > env > default).
### Шаг 1. Ключи
```bash
# Ключ identity ноды (Ed25519 — подпись блоков + tx)
./client keygen --out keys/node.json
# relay-ключ (X25519 — E2E-mailbox) создаётся нодой сам при первом старте,
# но можно задать путь заранее через --relay-key.
```
### Шаг 2a. Первая нода (genesis)
Поднимает новую сеть с одним валидатором. `--genesis=true` **только** для самой первой ноды и **только один раз** — если блок 0 уже есть в `--db`, флаг игнорируется.
```bash
./node \
--genesis=true \
--key=keys/node.json \
--db=./chaindata \
--mailbox-db=./mailboxdata \
--feed-db=./feeddata \
--listen=/ip4/0.0.0.0/tcp/4001 \
--announce=/ip4/<ВАШ-ПУБЛИЧНЫЙ-IP>/tcp/4001 \
--stats-addr=:8080 \
--register-relay=true \
--relay-fee=1000
```
`--announce` **обязателен** для любой ноды смотрящей в интернет (VPS / внешний IP / Docker с проброшенным портом). Без него libp2p пытается UPnP/NAT-PMP и чаще всего промахивается.
### Шаг 2b. Вторая и последующие ноды
Нужен **один** из двух способов узнать первую ноду. Второй удобнее.
**Через HTTP URL живой ноды** (рекомендуется — нода сама заберёт multiaddr через `/api/network-info`, проверит genesis_hash и синхронизирует цепь):
```bash
./node \
--join=https://first-node.example.com \
--key=keys/node.json \
--db=./chaindata \
--mailbox-db=./mailboxdata \
--feed-db=./feeddata \
--listen=/ip4/0.0.0.0/tcp/4001 \
--announce=/ip4/<ВАШ-ПУБЛИЧНЫЙ-IP>/tcp/4001 \
--stats-addr=:8080 \
--register-relay=true \
--relay-fee=1000
```
**Через libp2p multiaddr** (если есть прямой мульти-адрес):
```bash
./node \
--peers=/ip4/1.2.3.4/tcp/4001/p2p/12D3KooW... \
# остальные флаги как выше
```
**Автоприсоединение к validator set** происходит не само: после того как нода синхронизируется, действующий validator должен вызвать `client add-validator --target <your-pub> --cosigs ...` (multi-sig admit). До этого новая нода живёт как **observer** — читает и гоняет tx, но не голосует. Запустить ноду **явно** как observer (никогда не проситься в validator set): `--observer=true`.
### Все флаги `node`
CLI / env / default. Группы:
**Storage**
| Флаг | Env | Default | Назначение |
|------|-----|---------|-----------|
| `--db` | `DCHAIN_DB` | `./chaindata` | BadgerDB блокчейна |
| `--mailbox-db` | `DCHAIN_MAILBOX_DB` | `./mailboxdata` | E2E-конверты 1:1 чатов |
| `--feed-db` | `DCHAIN_FEED_DB` | `./feeddata` | Тела постов ленты (off-chain) |
| `--feed-ttl-days` | `DCHAIN_FEED_TTL_DAYS` | `30` | Через сколько дней тела постов auto-evict'ятся (метаданные on-chain остаются вечно) |
**Identity**
| Флаг | Env | Default | Назначение |
|------|-----|---------|-----------|
| `--key` | `DCHAIN_KEY` | `./node.json` | Ed25519 ключ ноды |
| `--relay-key` | `DCHAIN_RELAY_KEY` | `./relay.json` | X25519 ключ для relay-mailbox (создастся сам) |
| `--wallet` | `DCHAIN_WALLET` | — | Отдельный payout-кошелёк (опционально) |
| `--wallet-pass` | `DCHAIN_WALLET_PASS` | — | Парольная фраза для wallet-файла |
**Network**
| Флаг | Env | Default | Назначение |
|------|-----|---------|-----------|
| `--listen` | `DCHAIN_LISTEN` | `/ip4/0.0.0.0/tcp/4001` | libp2p listen multiaddr |
| `--announce` | `DCHAIN_ANNOUNCE` | — | Multiaddr который нода рассказывает пирам (обязателен на VPS/внешнем IP) |
| `--peers` | `DCHAIN_PEERS` | — | Bootstrap multiaddrs, comma-separated |
| `--join` | `DCHAIN_JOIN` | — | HTTP URL живой ноды для авто-дискавери — получает peers и genesis_hash |
| `--allow-genesis-mismatch` | — | `false` | Отключить защиту, падающую при расхождении локального и seed'ового genesis (только для явной миграции) |
**Consensus & role**
| Флаг | Env | Default | Назначение |
|------|-----|---------|-----------|
| `--genesis` | `DCHAIN_GENESIS` | `false` | Создать блок 0 (только для самой первой ноды сети) |
| `--validators` | `DCHAIN_VALIDATORS` | — | Исходный validator set (CSV pub-keys) — применяется только при genesis |
| `--observer` | `DCHAIN_OBSERVER` | `false` | Observer-режим: синхронизируется и отдаёт API, но не голосует и не предлагает блоки |
| `--heartbeat` | `DCHAIN_HEARTBEAT` | `true` | Периодический HEARTBEAT-tx (нужен для liveness-детекции валидаторов) |
**Relay / mailbox**
| Флаг | Env | Default | Назначение |
|------|-----|---------|-----------|
| `--register-relay` | `DCHAIN_REGISTER_RELAY` | `false` | Отправить `REGISTER_RELAY` tx на старте (объявить ноду публичным relay'ем) |
| `--relay-fee` | `DCHAIN_RELAY_FEE` | `1000` | Плата за доставку одного сообщения в µT (1000 = 0.001 T). `0` = бесплатный relay |
**Media scrubber (feed)**
| Флаг | Env | Default | Назначение |
|------|-----|---------|-----------|
| `--media-sidecar-url` | `DCHAIN_MEDIA_SIDECAR_URL` | — | URL FFmpeg-сайдкара для видео-скраба. Пустой = только картинки |
| `--allow-unscrubbed-video` | `DCHAIN_ALLOW_UNSCRUBBED_VIDEO` | `false` | Принимать видео **без** серверного скраба (опасно — EXIF/GPS/автор-теги останутся) |
**HTTP API**
| Флаг | Env | Default | Назначение |
|------|-----|---------|-----------|
| `--stats-addr` | `DCHAIN_STATS_ADDR` | `:8080` | Адрес HTTP/WS сервера |
| `--api-token` | `DCHAIN_API_TOKEN` | — | Bearer-токен для submit tx. Пустой = публичная нода |
| `--api-private` | `DCHAIN_API_PRIVATE` | `false` | Требовать токен также на чтение |
| `--disable-ui` | `DCHAIN_DISABLE_UI` | `false` | Отключить HTML-explorer (JSON API остаётся) |
| `--disable-swagger` | `DCHAIN_DISABLE_SWAGGER` | `false` | Отключить `/swagger*` |
**Resource caps** (новое в v2.1.0)
| Флаг | Env | Default | Назначение |
|------|-----|---------|-----------|
| `--max-cpu` | `DCHAIN_MAX_CPU` | `0` | Сколько CPU-ядер Go-runtime'у (`GOMAXPROCS`). `0` = все |
| `--max-ram-mb` | `DCHAIN_MAX_RAM_MB` | `0` | Soft-лимит Go-хипа в MiB (`GOMEMLIMIT`). `0` = без лимита. *Не OOM-kill'ит — усиливает GC при приближении* |
| `--feed-disk-limit-mb` | `DCHAIN_FEED_DISK_LIMIT_MB` | `0` | Жёсткая квота на feed-БД. При превышении `/feed/publish` отвечает 507. Существующие посты продолжают отдаваться |
| `--chain-disk-limit-mb` | `DCHAIN_CHAIN_DISK_LIMIT_MB` | `0` | Advisory-квота на блокчейн-БД. Превышение → `WARN` в лог раз в минуту (жёстко не отказываем — сломали бы консенсус) |
Для реального sandboxing (hard-kill при OOM, hard CPU throttling) используйте `docker run --cpus --memory` или systemd `CPUQuota` / `MemoryMax` поверх этих флагов.
**Update / versioning**
| Флаг | Env | Default | Назначение |
|------|-----|---------|-----------|
| `--update-source-url` | `DCHAIN_UPDATE_SOURCE_URL` | — | Gitea `/api/v1/repos/{owner}/{repo}/releases/latest` для `/api/update-check` |
| `--update-source-token` | `DCHAIN_UPDATE_SOURCE_TOKEN` | — | PAT для приватного репо |
| `--log-format` | `DCHAIN_LOG_FORMAT` | `text` | `text` (human) или `json` (Loki/ELK) |
| `--governance-contract` | `DCHAIN_GOVERNANCE_CONTRACT` | — | ID governance-контракта для динамических параметров |
| `--version` | — | — | Печатает версию и выходит |
### Минимальные чек-листы
**Первая нода (открытая):** `--genesis=true` + `--key` + `--announce` на внешний IP + `--stats-addr` + опционально `--register-relay=true --relay-fee=...` чтобы сразу монетизировать relay-трафик.
**Joiner:** `--join=<url-любой-живой-ноды>` + `--key` + `--announce` + `--stats-addr`. После синка попросите действующего валидатора поднять `add-validator` (иначе остаётесь observer'ом до принятия — это нормально и безопасно).
**Приватная/домашняя нода** без публичного эксплорера: добавьте `--api-token=<random>`, `--api-private=true`, `--disable-ui=true`, `--disable-swagger=true`. Clients передают `Authorization: Bearer <token>`.
**Слабое железо:** `--max-cpu=2 --max-ram-mb=1024 --feed-disk-limit-mb=2048 --chain-disk-limit-mb=10240`.
Docker-обёртка с теми же флагами — в [`deploy/single/README.md`](deploy/single/README.md).
## Продакшен деплой
Два варианта, по масштабу.