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
This commit is contained in:
vsecoder
2026-04-17 14:16:44 +03:00
commit 7e7393e4f8
196 changed files with 55947 additions and 0 deletions

219
docs/node/README.md Normal file
View File

@@ -0,0 +1,219 @@
# Запуск ноды
## Быстрый старт (Docker)
```bash
git clone https://github.com/your/go-blockchain
cd go-blockchain
docker compose up -d
```
Запускает 3 ноды: `node1` (8081), `node2` (8082), `node3` (8083).
Деплой контрактов:
```bash
docker exec node1 /scripts/deploy_contracts.sh
```
Explorer: http://localhost:8081
---
## Запуск вручную
### Требования
- Go 1.21+
- BadgerDB (встроен)
- libp2p (встроен)
### Сборка
```bash
cd go-blockchain
go build ./cmd/node/
go build ./cmd/client/
```
### Genesis нода
```bash
# Первый запуск — создать genesis
./node \
--key node1.json \
--genesis \
--validators "$(cat node1.json | jq -r .pub_key),$(cat node2.json | jq -r .pub_key),$(cat node3.json | jq -r .pub_key)" \
--stats-addr :8081 \
--listen /ip4/0.0.0.0/tcp/4001 \
--db ./data/node1
```
### Peer нода
```bash
./node \
--key node2.json \
--peers /ip4/127.0.0.1/tcp/4001/p2p/<node1-peer-id> \
--stats-addr :8082 \
--listen /ip4/0.0.0.0/tcp/4002 \
--db ./data/node2
```
---
## Флаги командной строки
| Флаг | По умолчанию | Описание |
|------|------------|---------|
| `--key` | `node.json` | Файл Ed25519 + X25519 идентичности |
| `--db` | `chaindata` | Директория BadgerDB |
| `--listen` | `/ip4/0.0.0.0/tcp/4001` | libp2p адрес |
| `--peers` | — | Bootstrap peer multiaddrs (через запятую) |
| `--validators` | — | Pubkeys валидаторов (через запятую, только для `--genesis`) |
| `--genesis` | false | Создать genesis блок при первом старте |
| `--stats-addr` | `:8080` | HTTP API/Explorer порт |
| `--wallet` | — | Payout кошелёк (hex pubkey) |
| `--wallet-pass` | — | Пароль к кошельку |
| `--heartbeat` | false | Отправлять heartbeat каждые 60 минут |
| `--register-relay` | false | Зарегистрироваться как relay-провайдер |
| `--relay-fee` | 0 | Fee за relay сообщение в µT |
| `--relay-key` | `relay.json` | X25519 ключ для relay шифрования |
| `--mailbox-db` | — | Директория для relay mailbox |
| `--governance-contract` | — | ID governance контракта для динамических параметров |
---
## Docker Compose
### docker-compose.yml структура
```yaml
services:
node1:
build: .
ports:
- "8081:8080" # HTTP API
- "4001:4001" # libp2p
volumes:
- node1_data:/chaindata
- ./keys:/keys:ro
command: >
node
--key /keys/node1.json
--genesis
--validators "$VALIDATORS"
--stats-addr :8080
--listen /ip4/0.0.0.0/tcp/4001
--governance-contract "$GOV_ID"
environment:
- VALIDATORS=03...,04...,05...
node2:
build: .
ports:
- "8082:8080"
command: >
node
--key /keys/node2.json
--peers /dns4/node1/tcp/4001/p2p/$NODE1_PEER_ID
--stats-addr :8080
--listen /ip4/0.0.0.0/tcp/4001
```
### Управление
```bash
# Запустить
docker compose up -d
# Логи
docker compose logs -f node1
# Остановить
docker compose down
# Сбросить данные
docker compose down -v
```
---
## Файл ключа
Каждая нода использует один файл с обоими ключами:
```json
{
"pub_key": "03abcd...", // Ed25519 pubkey (hex, 66 символов)
"priv_key": "...", // Ed25519 privkey (hex)
"x25519_pub": "...", // X25519 pubkey для E2E relay (hex, 64 символа)
"x25519_priv": "..." // X25519 privkey
}
```
Генерация:
```bash
client keygen --out node1.json
```
---
## Мониторинг
### HTTP healthcheck
```bash
curl http://localhost:8081/api/netstats
```
### Logs
```bash
# Docker
docker compose logs -f node1 | grep -E "block|error|warn"
# Прямой запуск
./node ... 2>&1 | tee node.log
```
### Метрики производительности
| Показатель | Норма |
|-----------|------|
| Время блока | ~3 с |
| Блоков в минуту | ~20 |
| PBFT фазы | prepare → commit → finalize |
---
## Структура данных BadgerDB
```
balance:<pubkey> → uint64 (µT)
identity:<pubkey> → JSON RegisterKeyPayload
stake:<pubkey> → uint64 (µT)
block:<index> → JSON Block
tx:<txid> → JSON TxRecord
txidx:<pubkey>:<block>:<seq> → txid (индекс по адресу)
contract:<id> → JSON ContractRecord
cstate:<id>:<key> → []byte (state контракта)
clog:<id>:<seq> → JSON ContractLogEntry
relay:<pubkey> → JSON RegisteredRelayInfo
```
---
## Сброс и восстановление
```bash
# Полный сброс
docker compose down -v
docker compose up -d
# Только данные одной ноды
docker compose stop node1
docker volume rm go-blockchain_node1_data
docker compose up -d node1
```
После сброса нужно заново задеплоить контракты и залинковать governance.

118
docs/node/governance.md Normal file
View File

@@ -0,0 +1,118 @@
# Governance интеграция
Нода может использовать governance-контракт для динамического управления параметрами (gas_price и другими) без перезапуска.
## Принцип работы
```
Governance contract (on-chain)
state: param:gas_price = "5"
param:relay_fee = "200"
...
Node (blockchain/chain.go)
GetEffectiveGasPrice() → читает BadgerDB напрямую
GetGovParam(key) → cstate:<govID>:param:<key>
```
Нода читает параметры **напрямую из BadgerDB** без вызова VM — это быстро и дёшево.
## Привязка governance
### При запуске ноды (флаг)
```bash
./node \
--key node1.json \
--governance-contract a1b2c3d4e5f60001 \
...
```
### В рантайме (без перезапуска)
```bash
curl -X POST http://localhost:8081/api/governance/link \
-H "Content-Type: application/json" \
-d '{"governance": "a1b2c3d4e5f60001"}'
```
**Response:**
```json
{"status": "ok", "governance": "a1b2c3d4e5f60001"}
```
Изменение вступает в силу немедленно — следующий `CALL_CONTRACT` уже будет использовать новый governance.
### Через deploy-скрипт (автоматически)
`scripts/deploy_contracts.sh` автоматически вызывает `link_governance` на всех нодах после деплоя:
```bash
link_governance() {
for NODE in http://node1:8080 http://node2:8080 http://node3:8080; do
curl -sX POST "$NODE/api/governance/link" \
-H "Content-Type: application/json" \
-d "{\"governance\":\"$GOV_ID\"}"
done
}
```
## Управляемые параметры
| Ключ | Тип | По умолчанию | Описание |
|------|-----|-------------|---------|
| `gas_price` | uint64 (строка) | 1 | µT за 1 gas unit |
Дополнительные параметры можно хранить в governance для читающих их контрактов (например, `messenger_entry_fee`, `relay_fee_override`, etc.) — нода их не читает, но они доступны через inter-contract вызовы.
## Изменение gas_price
```bash
# 1. Любой предлагает новое значение
client call-contract --method propose \
--arg gas_price --arg 5 \
--contract $GOV_ID --key key.json \
--gas 10000 --node http://node1:8080
# 2. Admin утверждает
client call-contract --method approve \
--arg gas_price \
--contract $GOV_ID --key /keys/node1.json \
--gas 10000 --node http://node1:8080
# 3. Проверить текущее значение
curl "http://localhost:8081/api/contracts/$GOV_ID/state/param:gas_price"
```
После approve — изменение вступает в силу немедленно на всех нодах которые привязали этот governance.
## Передача роли admin
```bash
# Передать admin другому валидатору
client call-contract --method set_admin \
--arg $NEW_ADMIN_PUBKEY \
--contract $GOV_ID --key /keys/node1.json \
--gas 10000 --node http://node1:8080
```
## Отвязать governance
Если нужно вернуться к значениям по умолчанию:
```bash
curl -X POST http://localhost:8081/api/governance/link \
-H "Content-Type: application/json" \
-d '{"governance": ""}'
```
После этого нода будет использовать встроенные константы (`GasPrice = 1 µT/gas`).
## Проверка привязки
Прямого API для проверки текущего govContractID нет. Косвенно: если gas_price изменился через governance и применился — привязка работает.
```bash
# Установить gas_price = 1 через governance
# Затем вызвать контракт с --gas 1000 и проверить fee = 1000 µT
```

291
docs/node/multi-server.md Normal file
View File

@@ -0,0 +1,291 @@
# Деплой на реальные серверы
Руководство по запуску трёх полноценных нод на разных VPS/серверах в интернете.
## Концепция
```
Server A (1.2.3.11) Server B (1.2.3.12) Server C (1.2.3.13)
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ node1 │ │ node2 │ │ node3 │
│ validator │◀────────▶│ validator │◀────────▶│ validator │
│ relay (2000µT) │ │ relay (1500µT) │ │ relay (1000µT) │
│ :4001 :8080 │ │ :4001 :8080 │ │ :4001 :8080 │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
└────────────────────────────┴─────────────────────────────┘
libp2p P2P
gossipsub: tx + blocks
streams: PBFT consensus, sync
```
Каждая нода — полноценный **валидатор** (участвует в PBFT-консенсусе) и **relay-провайдер** (хранит и доставляет зашифрованные сообщения).
## Требования
- Ubuntu 22.04 / Debian 12 (или любой Linux)
- Открытый TCP-порт `4001` (P2P) и опционально `8080` (HTTP API)
- Go 1.21+ **только для сборки** (на сервере нужен только бинарник)
## 1. Подготовка ключей
На любой машине (ноутбук / CI):
```bash
# Сгенерировать 3 ключа
./client keygen --out keys/node1.json
./client keygen --out keys/node2.json
./client keygen --out keys/node3.json
# Получить pubkey и peer ID для каждого
./peerid --key keys/node1.json --ip <SERVER_A_IP> --port 4001
./peerid --key keys/node2.json --ip <SERVER_B_IP> --port 4001
./peerid --key keys/node3.json --ip <SERVER_C_IP> --port 4001
```
Вывод `peerid`:
```
pub_key: 26018d40...
peer_id: 12D3KooW...
multiaddr: /ip4/1.2.3.11/tcp/4001/p2p/12D3KooW...
```
Запишите `pub_key` и `peer_id` для каждой ноды — они нужны в конфигурации.
## 2. Сборка бинарника
```bash
git clone https://github.com/your/go-blockchain
cd go-blockchain
CGO_ENABLED=0 GOOS=linux go build -trimpath -o node ./cmd/node
CGO_ENABLED=0 GOOS=linux go build -trimpath -o client ./cmd/client
# Скопировать на серверы
scp node client root@1.2.3.11:/usr/local/bin/
scp node client root@1.2.3.12:/usr/local/bin/
scp node client root@1.2.3.13:/usr/local/bin/
```
## 3. Копирование ключей
```bash
# Каждый сервер получает ТОЛЬКО свой ключ
scp keys/node1.json root@1.2.3.11:/etc/dchain/node.json
scp keys/node2.json root@1.2.3.12:/etc/dchain/node.json
scp keys/node3.json root@1.2.3.13:/etc/dchain/node.json
chmod 600 /etc/dchain/node.json # на каждом сервере
```
## 4. Переменные конфигурации
Подставьте реальные значения из шага 1:
```bash
# Validators — все три pubkey через запятую
VALIDATORS="<NODE1_PUB>,<NODE2_PUB>,<NODE3_PUB>"
# Bootstrap multiaddrs для node2 и node3
NODE1_PEER="/ip4/1.2.3.11/tcp/4001/p2p/<NODE1_PEER_ID>"
NODE2_PEER="/ip4/1.2.3.12/tcp/4001/p2p/<NODE2_PEER_ID>"
```
## 5. Запуск нод
### Server A — node1 (genesis + validator + relay)
```bash
node \
--genesis \
--key /etc/dchain/node.json \
--db /var/lib/dchain/chain \
--mailbox-db /var/lib/dchain/mailbox \
--listen /ip4/0.0.0.0/tcp/4001 \
--announce /ip4/1.2.3.11/tcp/4001 \
--stats-addr :8080 \
--validators "$VALIDATORS" \
--heartbeat=true \
--register-relay \
--relay-fee 2000
```
### Server B — node2 (validator + relay)
```bash
node \
--key /etc/dchain/node.json \
--db /var/lib/dchain/chain \
--mailbox-db /var/lib/dchain/mailbox \
--listen /ip4/0.0.0.0/tcp/4001 \
--announce /ip4/1.2.3.12/tcp/4001 \
--stats-addr :8080 \
--validators "$VALIDATORS" \
--peers "$NODE1_PEER" \
--heartbeat=true \
--register-relay \
--relay-fee 1500
```
### Server C — node3 (validator + relay)
```bash
node \
--key /etc/dchain/node.json \
--db /var/lib/dchain/chain \
--mailbox-db /var/lib/dchain/mailbox \
--listen /ip4/0.0.0.0/tcp/4001 \
--announce /ip4/1.2.3.13/tcp/4001 \
--stats-addr :8080 \
--validators "$VALIDATORS" \
--peers "$NODE1_PEER,$NODE2_PEER" \
--heartbeat=true \
--register-relay \
--relay-fee 1000
```
## 6. systemd unit
Создайте `/etc/systemd/system/dchain.service` на каждом сервере:
```ini
[Unit]
Description=DChain Node
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=dchain
ExecStart=/usr/local/bin/node \
--key /etc/dchain/node.json \
--db /var/lib/dchain/chain \
--mailbox-db /var/lib/dchain/mailbox \
--listen /ip4/0.0.0.0/tcp/4001 \
--announce /ip4/YOUR_PUBLIC_IP/tcp/4001 \
--stats-addr :8080 \
--validators "V1_PUB,V2_PUB,V3_PUB" \
--peers "/ip4/SEED_IP/tcp/4001/p2p/SEED_PEER_ID" \
--heartbeat=true \
--register-relay \
--relay-fee 1000
Restart=on-failure
RestartSec=5s
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
```
```bash
# Применить
sudo systemctl daemon-reload
sudo systemctl enable --now dchain
# Логи
sudo journalctl -u dchain -f
```
## 7. Деплой контрактов
После запуска всех трёх нод:
```bash
# С любой машины имеющей client и ключ node1
export NODE_URL=http://1.2.3.11:8080
export KEY=/etc/dchain/node.json # или локальная копия
# Задеплоить username_registry
CONTRACT_ID=$(client deploy-contract \
--key $KEY \
--wasm contracts/username_registry/username_registry.wasm \
--abi contracts/username_registry/username_registry_abi.json \
--node $NODE_URL | grep contract_id | awk '{print $2}')
echo "username_registry: $CONTRACT_ID"
# Залинковать governance на всех трёх нодах
for NODE in http://1.2.3.11:8080 http://1.2.3.12:8080 http://1.2.3.13:8080; do
curl -sX POST "$NODE/api/governance/link" \
-H "Content-Type: application/json" \
-d "{\"governance\":\"$GOV_ID\"}"
done
```
## 8. Как работает сеть
### Bootstrap и discovery
```
node2 старт:
1. --peers → connectWithRetry("/ip4/1.2.3.11/tcp/4001/p2p/...")
Подключается к node1 (бесконечный retry с backoff 1→30s)
2. При connect → syncOnConnect()
Запрашивает все блоки которых нет у node2
3. Kademlia DHT bootstrap (через node1)
DHT узнаёт о node3 → DiscoverPeers() подключается
4. mDNS (только LAN/Docker) — игнорируется в интернете
5. connectWithRetry keep-alive: каждые 30s проверяет связь,
автоматически переподключает при обрыве
```
### Консенсус
PBFT 3-of-3, fault tolerance f=1:
- Для коммита блока нужны подписи **2 из 3** валидаторов
- Если одна нода упала → сеть продолжает работать
- Если упали две → сеть ждёт (не производит блоки)
### --announce и адреса
Без `--announce` libp2p рекламирует **все** адреса интерфейсов:
- `0.0.0.0` → раскрывается в loopback и внутренние адреса
- Другие ноды получают `127.0.0.1:4001` → не могут подключиться
С `--announce /ip4/1.2.3.11/tcp/4001`:
- Единственный рекламируемый адрес — публичный IP сервера
- `AddrStrings()` возвращает только этот адрес
- DHT propagates этот адрес другим нодам в сети
## 9. Firewall
```bash
# UFW
ufw allow 4001/tcp # P2P — обязательно
ufw allow 8080/tcp # HTTP API — опционально (для Explorer, деплоя контрактов)
# iptables
iptables -A INPUT -p tcp --dport 4001 -j ACCEPT
iptables -A INPUT -p tcp --dport 8080 -j ACCEPT
```
## 10. Добавление новой ноды в сеть
Новый участник (не валидатор, только синхронизация и relay):
```bash
node \
--key /etc/dchain/node.json \
--db /var/lib/dchain/chain \
--mailbox-db /var/lib/dchain/mailbox \
--listen /ip4/0.0.0.0/tcp/4001 \
--announce /ip4/YOUR_IP/tcp/4001 \
--stats-addr :8080 \
--validators "$VALIDATORS" \
--peers "$NODE1_PEER" \
--heartbeat=false \
--register-relay \
--relay-fee 500
```
Нода автоматически:
1. Подключится к node1 через `--peers`
2. Синхронизирует всю историю блоков
3. Через DHT найдёт node2 и node3
4. Начнёт получать и пересылать relay-сообщения
Чтобы сделать её валидатором — нужна `ADD_VALIDATOR` транзакция от существующего валидатора.