chore(release): clean up repo for v0.0.1 release

Excluded from release bundle:
- CONTEXT.md, CHANGELOG.md (agent/project working notes)
- client-app/ (React Native messenger — tracked separately)
- contracts/hello_go/ (unused standalone example)

Kept contracts/counter/ and contracts/name_registry/ as vm-test fixtures
(referenced by vm/vm_test.go; NOT production contracts).

Docs refactor:
- docs/README.md — new top-level index with cross-references
- docs/quickstart.md — rewrite around single-node as primary path
- docs/node/README.md — full rewrite, all CLI flags, schema table
- docs/api/README.md — add /api/well-known-version, /api/update-check
- docs/contracts/README.md — split native (Go) vs WASM (user-deployable)
- docs/update-system.md — new, full 5-layer update system design
- README.md — link into docs/, drop CHANGELOG/client-app references

Build-time version system (inherited from earlier commits this branch):
- node --version / client --version with ldflags-injected metadata
- /api/well-known-version with {build, protocol_version, features[]}
- Peer-version gossip on dchain/version/v1
- /api/update-check against Gitea release API
- deploy/single/update.sh with semver guard + 15-min systemd jitter
This commit is contained in:
vsecoder
2026-04-17 14:37:00 +03:00
parent 7e7393e4f8
commit 546d2c503f
55 changed files with 702 additions and 17381 deletions

201
docs/update-system.md Normal file
View File

@@ -0,0 +1,201 @@
# Система обновлений и версионирование
DChain поставляется с полноценной системой update-detection, которая
включает **пять слоёв**: build-time версия в бинаре, HTTP-эндпоинты для
обнаружения, peer-gossip версий соседей, `update-check` от Gitea, и
rolling-restart скрипт. Ниже — как это работает и как использовать.
## Слой 1. Build-time версия
Бинарь хранит 4 поля, инжектимые через `-ldflags -X`:
- `Tag` — человекочитаемый тег, обычно `git describe --tags --always --dirty`
- `Commit` — полный 40-символьный SHA коммита
- `Date` — RFC 3339 timestamp сборки (UTC)
- `Dirty``"true"` если сборка была из грязного worktree
Печать:
```bash
node --version
# dchain-node v0.0.1 (commit=abc1234 date=2026-04-17T10:00:00Z dirty=false)
client --version
# тот же формат
```
Все 4 образа (`Dockerfile` и `deploy/prod/Dockerfile.slim`) принимают эти
значения через `--build-arg VERSION_TAG=…` и т.д. `update.sh`
вычисляет их автоматически перед ребилдом.
## Слой 2. `/api/well-known-version`
```bash
curl -s http://localhost:8080/api/well-known-version | jq .
```
```json
{
"node_version": "v0.0.1",
"build": {
"tag": "v0.0.1",
"commit": "abc1234…",
"date": "2026-04-17T10:00:00Z",
"dirty": "false"
},
"protocol_version": 1,
"features": [
"access_token", "chain_id", "channels_v1", "contract_logs",
"fan_out", "identity_registry", "native_username_registry",
"onboarding_api", "payment_channels", "relay_mailbox",
"ws_submit_tx"
],
"chain_id": "dchain-ddb9a7e37fc8"
}
```
Клиент использует это для feature-detection: зная `features[]`, он знает
какие экраны/флоу рендерить. Пример в `client-app/lib/api.ts`
функция `checkNodeVersion()`.
**Protocol version** — отдельная ось от `node_version`. Меняется только
при несовместимых изменениях wire-протокола (новый формат PBFT-message,
breaking change в tx encoding). Клиент, который ожидает `protocol_version: 1`,
не должен работать с нодой `protocol_version: 2`.
## Слой 3. Peer-gossip версий
Каждая нода публикует своё `{peer_id, tag, commit, protocol_version,
timestamp}` в gossipsub-топик `dchain/version/v1` раз в 60 секунд. Другие
ноды эту информацию получают, хранят 15 минут, и отдают через
`/api/peers`:
```bash
curl -s http://localhost:8080/api/peers | jq .
```
```json
{
"peers": [
{
"id": "12D3KooW…",
"addrs": ["/ip4/…/tcp/4001/p2p/12D3KooW…"],
"version": {
"tag": "v0.0.1",
"commit": "abc1234…",
"protocol_version": 1,
"timestamp": 1745000000,
"received_at": "2026-04-17T10:01:00Z"
}
}
]
}
```
Зачем это:
- Operator видит «какая версия у моих соседей» одним запросом.
- Client видит «стоит ли переключиться на другую ноду».
- Feature-flag activation (например, `EventChannelBan`) можно запускать
только когда ≥80% сети на новой версии.
## Слой 4. `/api/update-check`
Оператор настраивает `DCHAIN_UPDATE_SOURCE_URL` на ссылку вида:
```
https://<your-gitea>/api/v1/repos/<owner>/<repo>/releases/latest
```
Нода опрашивает этот URL (15 мин cache, 5 сек timeout) и возвращает:
```bash
curl -s http://localhost:8080/api/update-check | jq .
```
```json
{
"current": { "tag": "v0.0.1", "commit": "…", "date": "…", "dirty": "false" },
"latest": { "tag": "v0.0.2", "commit": "…", "url": "https://…", "published_at": "…" },
"update_available": true,
"checked_at": "2026-04-17T11:00:00Z",
"source": "https://git.vsecoder.vodka/api/v1/repos/vsecoder/dchain/releases/latest"
}
```
- 503 — не настроен `DCHAIN_UPDATE_SOURCE_URL`.
- 502 — upstream (Gitea) недоступен или ответил non-2xx.
- `update_available: false` — либо HEAD совпадает, либо Gitea вернула
draft/prerelease (оба игнорируются).
Токен для приватного репо: `DCHAIN_UPDATE_SOURCE_TOKEN=<Gitea PAT>`
(scope: `read:repository` достаточно).
## Слой 5. `update.sh` + systemd timer
Скрипт в `deploy/single/update.sh`. Флоу:
1. Если `DCHAIN_UPDATE_SOURCE_URL` задан — сначала спрашивает
`/api/update-check`. Если `update_available: false` — выход с кодом 0.
2. `git fetch --tags`.
3. **Semver guard:** если `UPDATE_ALLOW_MAJOR != true` и major-версия
меняется (v1.x → v2.y) — блокирует обновление с exit code 4.
4. `git checkout <tag>` (в detached HEAD) или ff-merge на `origin/main`.
5. Ребилд образа с правильными `--build-arg VERSION_*`.
6. Smoke-test: `docker run --rm … node --version` — должен напечатать
новую версию без ошибок.
7. `docker compose up -d --force-recreate node`.
8. Polling `/api/netstats` до 60 сек — если не ожил, `exit 1`.
Systemd-интеграция — в `deploy/single/systemd/`:
```bash
sudo cp deploy/single/systemd/dchain-update.{service,timer} /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now dchain-update.timer
```
Таймер: `OnUnitActiveSec=1h` + `RandomizedDelaySec=15min` — чтобы
федерация не рестартовала вся одновременно и не уронила PBFT quorum.
## Schema migrations (BadgerDB)
Отдельный слой, относящийся к on-disk формату данных. См.
`blockchain/schema_migrations.go`:
- `CurrentSchemaVersion` — const в Go-коде, bumpается с каждой
миграцией.
- `schemaMetaKey = "schema:ver"` — ключ в BadgerDB хранит фактическую
версию данных.
- `runMigrations(db)` вызывается при `NewChain()`, применяет каждый
шаг форвард-миграций атомарно (data rewrite + version bump в одной
badger.Update транзакции).
- Если stored version > CurrentSchemaVersion — ошибка (запускаете
старый бинарь на новом DB). Fix: обновите бинарь или восстановите
из бэкапа.
На текущий релиз `CurrentSchemaVersion = 0`, миграций нет — scaffold
живёт и готов к первому реальному изменению формата.
## Forward-compat для EventType
В `blockchain/chain.go``applyTx()` добавлен `default:` case для
неизвестных `EventType`:
- Fee дебитуется с отправителя (не спам-вектор).
- Tx применяется как **no-op**.
- В логе warning «unknown event type … — binary is older than this tx».
Это значит: новую `EventType` можно подать в сеть, она будет
обработана на новых нодах и проигнорирована на старых, без split'а
консенсуса. Full design — `deploy/UPDATE_STRATEGY.md` → §5.1.
## Checklist при релизе
1. В Git `git tag -a vX.Y.Z -m "release notes"` + `git push origin vX.Y.Z`.
2. В Gitea UI: `Releases → New Release → Tag: vX.Y.Z → Publish`.
3. На всех нодах с настроенным `update.sh`:
- systemd-таймер подхватит через ~1 час (± 15 мин jitter).
- Operator может форсить: `sudo systemctl start dchain-update.service`.
4. Проверка:
```bash
curl -s https://<node>/api/well-known-version | jq .build.tag
# должно вернуть vX.Y.Z
```