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:
59
docs/contracts/README.md
Normal file
59
docs/contracts/README.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Production Smart Contracts
|
||||
|
||||
DChain поставляется с четырьмя production-контрактами, задеплоенными из genesis-кошелька.
|
||||
|
||||
## Обзор
|
||||
|
||||
| Контракт | Назначение | Ключевые фичи |
|
||||
|---------|-----------|--------------|
|
||||
| [username_registry](username_registry.md) | Username ↔ адрес | Тарифная сетка, treasury fees, reverse lookup |
|
||||
| [governance](governance.md) | On-chain параметры | Propose/approve workflow, admin role |
|
||||
| [auction](auction.md) | English auction | Token escrow, автоматический refund, settle |
|
||||
| [escrow](escrow.md) | Двусторонний escrow | Dispute/resolve, admin arbitration |
|
||||
|
||||
## Деплой
|
||||
|
||||
```bash
|
||||
docker compose --profile deploy run --rm deploy
|
||||
```
|
||||
|
||||
Все 4 контракта деплоятся автоматически. ID сохраняются в `/tmp/contracts.env`.
|
||||
|
||||
## Вызов контракта
|
||||
|
||||
```bash
|
||||
docker exec node1 client call-contract \
|
||||
--key /keys/node1.json \
|
||||
--contract <CONTRACT_ID> \
|
||||
--method <METHOD> \
|
||||
--arg <STRING_ARG> # строковый аргумент (можно несколько)
|
||||
--arg64 <UINT64_ARG> # числовой аргумент uint64
|
||||
--gas <GAS_LIMIT> # рекомендуется 20000 для записи, 5000 для чтения
|
||||
--node http://node1:8080
|
||||
```
|
||||
|
||||
## Contract Treasury
|
||||
|
||||
У каждого контракта есть **ownerless treasury address** — `hex(sha256(contractID + ":treasury"))`.
|
||||
Это эскроу-адрес без private key. Только сам контракт может снять с него деньги через host function `transfer`.
|
||||
|
||||
Используется в `auction` и `escrow` для хранения заблокированных токенов.
|
||||
|
||||
## Просмотр состояния
|
||||
|
||||
```bash
|
||||
# Через REST API
|
||||
curl http://localhost:8081/api/contracts/<ID>/state/<key>
|
||||
|
||||
# Через Explorer
|
||||
open http://localhost:8081/contract?id=<ID>
|
||||
```
|
||||
|
||||
## Логи контракта
|
||||
|
||||
```bash
|
||||
# REST
|
||||
curl "http://localhost:8081/api/contracts/<ID>/logs?limit=20"
|
||||
|
||||
# Explorer → вкладка Logs
|
||||
```
|
||||
213
docs/contracts/auction.md
Normal file
213
docs/contracts/auction.md
Normal file
@@ -0,0 +1,213 @@
|
||||
# auction
|
||||
|
||||
English auction с on-chain token escrow. Ставки хранятся в contract treasury. При перебивании ставки предыдущий топ-биддер получает автоматический refund.
|
||||
|
||||
## Жизненный цикл
|
||||
|
||||
```
|
||||
seller buyer-1 buyer-2 anyone
|
||||
│ │ │ │
|
||||
│─ create ──────▶│ │ │
|
||||
│ (min_bid, │ │ │
|
||||
│ duration) │ │ │
|
||||
│ │ │ │
|
||||
│ │─ bid(500) ───▶│ │
|
||||
│ │ treasury ←500│ │
|
||||
│ │ │ │
|
||||
│ │ │─ bid(800) ───▶│
|
||||
│ │ refund→500 │ treasury ←800│
|
||||
│ │ │ │
|
||||
│ │ │ │ [end_block reached]
|
||||
│ │ │ │
|
||||
│◀─────────────────────────────────── settle ────│
|
||||
│ treasury→800 │ │ │
|
||||
│ (seller gets │ │ │
|
||||
│ winning bid) │ │ │
|
||||
```
|
||||
|
||||
**Статусы:** `open` → `settled` / `cancelled`
|
||||
|
||||
## Auction ID
|
||||
|
||||
Формат: `<block_height>:<seq>`, например `42:0`, `42:1`.
|
||||
Генерируется автоматически при `create`, логируется как `created: <id>`.
|
||||
|
||||
## Методы
|
||||
|
||||
### `create`
|
||||
|
||||
Создать новый аукцион.
|
||||
|
||||
**Аргументы:**
|
||||
| # | Имя | Тип | Описание |
|
||||
|---|-----|-----|---------|
|
||||
| 0 | `title` | string | Описание лота (max 128 байт) |
|
||||
| 1 | `min_bid` | uint64 | Минимальная ставка в µT |
|
||||
| 2 | `duration` | uint64 | Длительность в блоках |
|
||||
|
||||
**Поведение:**
|
||||
- Сохраняет seller = caller
|
||||
- `end_block = current_block + duration`
|
||||
- Лог: `created: <auction_id>`
|
||||
|
||||
```bash
|
||||
# Аукцион: мин. ставка 1 T, длительность 100 блоков
|
||||
client call-contract --method create \
|
||||
--arg "Rare NFT #42" --arg64 1000000 --arg64 100 \
|
||||
--contract $AUC_ID --key /keys/node1.json \
|
||||
--gas 20000 --node http://node1:8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `bid`
|
||||
|
||||
Поставить ставку. Средства переводятся из кошелька caller в treasury контракта.
|
||||
|
||||
**Аргументы:**
|
||||
| # | Имя | Тип | Описание |
|
||||
|---|-----|-----|---------|
|
||||
| 0 | `auction_id` | string | ID аукциона |
|
||||
| 1 | `amount` | uint64 | Ставка в µT |
|
||||
|
||||
**Проверки:**
|
||||
- Аукцион в статусе `open`
|
||||
- `current_block ≤ end_block`
|
||||
- `amount > current_top_bid` (или `amount ≥ min_bid` если ставок нет)
|
||||
|
||||
**Поведение:**
|
||||
- Переводит `amount` с caller → treasury
|
||||
- Если был предыдущий топ-биддер → refund ему его ставки из treasury
|
||||
- Обновляет `top_bidder` и `top_bid`
|
||||
- Лог: `bid: <auction_id>`
|
||||
|
||||
```bash
|
||||
client call-contract --method bid \
|
||||
--arg "42:0" --arg64 2000000 \
|
||||
--contract $AUC_ID --key /keys/node1.json \
|
||||
--gas 30000 --node http://node1:8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `settle`
|
||||
|
||||
Завершить аукцион после истечения `end_block`. Переводит топ-ставку продавцу.
|
||||
|
||||
**Аргументы:**
|
||||
| # | Имя | Тип |
|
||||
|---|-----|-----|
|
||||
| 0 | `auction_id` | string |
|
||||
|
||||
**Проверки:** `current_block > end_block`, статус `open`
|
||||
|
||||
**Поведение:**
|
||||
- Если есть ставки: переводит `top_bid` из treasury → seller, статус → `settled`
|
||||
- Если ставок нет: статус → `cancelled`
|
||||
- Лог: `settled: <id>` или `cancelled: <id> (no bids)`
|
||||
- Может вызвать **любой** (не только seller)
|
||||
|
||||
```bash
|
||||
client call-contract --method settle --arg "42:0" \
|
||||
--contract $AUC_ID --key /keys/node1.json \
|
||||
--gas 20000 --node http://node1:8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `cancel`
|
||||
|
||||
Отменить аукцион без ставок. Только seller.
|
||||
|
||||
**Аргументы:**
|
||||
| # | Имя | Тип |
|
||||
|---|-----|-----|
|
||||
| 0 | `auction_id` | string |
|
||||
|
||||
**Проверки:** статус `open`, ставок нет, `caller == seller`
|
||||
|
||||
```bash
|
||||
client call-contract --method cancel --arg "42:0" \
|
||||
--contract $AUC_ID --key /keys/node1.json \
|
||||
--gas 10000 --node http://node1:8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `info`
|
||||
|
||||
Запросить состояние аукциона.
|
||||
|
||||
**Аргументы:**
|
||||
| # | Имя | Тип |
|
||||
|---|-----|-----|
|
||||
| 0 | `auction_id` | string |
|
||||
|
||||
**Логи:**
|
||||
```
|
||||
seller: <pubkey>
|
||||
title: <title>
|
||||
top_bid: <amount>
|
||||
end_block: <block>
|
||||
status: open|settled|cancelled
|
||||
```
|
||||
|
||||
```bash
|
||||
client call-contract --method info --arg "42:0" \
|
||||
--contract $AUC_ID --key /keys/node1.json \
|
||||
--gas 5000 --node http://node1:8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## State Layout
|
||||
|
||||
```
|
||||
cstate:<contractID>:seq → uint64 (глобальный счётчик аукционов)
|
||||
cstate:<contractID>:a:<id>:s → seller pubkey
|
||||
cstate:<contractID>:a:<id>:t → title
|
||||
cstate:<contractID>:a:<id>:b → top_bid (uint64 big-endian)
|
||||
cstate:<contractID>:a:<id>:e → end_block (uint64 big-endian)
|
||||
cstate:<contractID>:a:<id>:w → top_bidder pubkey
|
||||
cstate:<contractID>:a:<id>:x → status byte ('o', 's', 'c')
|
||||
```
|
||||
|
||||
## Полный пример сценария
|
||||
|
||||
```bash
|
||||
# 1. Alice создаёт аукцион
|
||||
docker exec node1 client call-contract \
|
||||
--key /keys/node1.json --contract $AUC_ID \
|
||||
--method create \
|
||||
--arg "Special Edition #1" --arg64 500000 --arg64 50 \
|
||||
--gas 20000 --node http://node1:8080
|
||||
# Лог: created: 5:0
|
||||
|
||||
AUC_ITEM="5:0"
|
||||
|
||||
# 2. Bob делает ставку 1 T
|
||||
docker exec node1 client call-contract \
|
||||
--key /tmp/bob.json --contract $AUC_ID \
|
||||
--method bid --arg $AUC_ITEM --arg64 1000000 \
|
||||
--gas 30000 --node http://node1:8080
|
||||
|
||||
# 3. Charlie перебивает ставку — Bob получает refund автоматически
|
||||
docker exec node1 client call-contract \
|
||||
--key /tmp/charlie.json --contract $AUC_ID \
|
||||
--method bid --arg $AUC_ITEM --arg64 1500000 \
|
||||
--gas 30000 --node http://node1:8080
|
||||
|
||||
# 4. Проверить статус
|
||||
docker exec node1 client call-contract \
|
||||
--key /keys/node1.json --contract $AUC_ID \
|
||||
--method info --arg $AUC_ITEM \
|
||||
--gas 5000 --node http://node1:8080
|
||||
|
||||
# 5. После end_block — завершить (любой может)
|
||||
docker exec node1 client call-contract \
|
||||
--key /keys/node1.json --contract $AUC_ID \
|
||||
--method settle --arg $AUC_ITEM \
|
||||
--gas 20000 --node http://node1:8080
|
||||
# Лог: settled: 5:0
|
||||
# Alice получает 1.5 T на кошелёк
|
||||
```
|
||||
267
docs/contracts/escrow.md
Normal file
267
docs/contracts/escrow.md
Normal file
@@ -0,0 +1,267 @@
|
||||
# escrow
|
||||
|
||||
Двусторонний trustless escrow. Buyer блокирует средства в contract treasury. Seller выполняет условие. Buyer подтверждает или открывает спор. При споре admin-арбитр решает исход.
|
||||
|
||||
## Жизненный цикл
|
||||
|
||||
```
|
||||
buyer seller admin
|
||||
│ │ │
|
||||
│─ create ─────▶│ │ treasury ← amount (locked)
|
||||
│ (seller, │ │ status: active
|
||||
│ amount) │ │
|
||||
│ │ │
|
||||
│ [seller delivered] │
|
||||
│ │ │
|
||||
│─ release ────▶│ │ treasury → seller
|
||||
│ │ │ status: released
|
||||
|
||||
OR:
|
||||
|
||||
│─ dispute ────▶│ │ status: disputed
|
||||
│ │─ dispute ────▶│ (either party can)
|
||||
│ │ │
|
||||
│ │ │─ resolve(winner=buyer) ──▶
|
||||
│ │ │ treasury → buyer
|
||||
│ │ │ status: refunded
|
||||
│ │ │
|
||||
│ │ │─ resolve(winner=seller) ──▶
|
||||
│ │ │ treasury → seller
|
||||
│ │ │ status: released
|
||||
|
||||
OR:
|
||||
|
||||
│ │─ refund ─────▶│ treasury → buyer (voluntary)
|
||||
│ │ │ status: refunded
|
||||
```
|
||||
|
||||
**Статусы:** `active` → `released` / `refunded` / `disputed`
|
||||
|
||||
## Методы
|
||||
|
||||
### `init`
|
||||
|
||||
Установить caller как admin (арбитр споров).
|
||||
|
||||
**Аргументы:** нет
|
||||
|
||||
Вызывается один раз после деплоя (deploy-скрипт делает автоматически).
|
||||
|
||||
```bash
|
||||
client call-contract --method init \
|
||||
--contract $ESC_ID --key /keys/node1.json \
|
||||
--gas 5000 --node http://node1:8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `create`
|
||||
|
||||
Buyer создаёт escrow и блокирует средства.
|
||||
|
||||
**Аргументы:**
|
||||
| # | Имя | Тип | Описание |
|
||||
|---|-----|-----|---------|
|
||||
| 0 | `id` | string | Уникальный ID эскроу (задаёт пользователь) |
|
||||
| 1 | `seller` | string | Pubkey продавца (hex) |
|
||||
| 2 | `amount` | uint64 | Сумма в µT |
|
||||
|
||||
**Поведение:**
|
||||
- Переводит `amount` с caller (buyer) → treasury
|
||||
- Сохраняет buyer, seller, amount, status=active
|
||||
- Лог: `created: <id>`
|
||||
|
||||
**Ограничения:**
|
||||
- ID должен быть уникальным — если `cstate[e:<id>:b]` уже есть, tx отклоняется
|
||||
|
||||
```bash
|
||||
client call-contract --method create \
|
||||
--arg "deal-001" \
|
||||
--arg <SELLER_PUBKEY> \
|
||||
--arg64 10000000 \
|
||||
--contract $ESC_ID --key /tmp/buyer.json \
|
||||
--gas 30000 --node http://node1:8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `release`
|
||||
|
||||
Buyer подтверждает получение и освобождает средства seller'у.
|
||||
|
||||
**Аргументы:**
|
||||
| # | Имя | Тип |
|
||||
|---|-----|-----|
|
||||
| 0 | `id` | string |
|
||||
|
||||
**Права:** только buyer.
|
||||
**Статус:** должен быть `active`.
|
||||
|
||||
**Поведение:**
|
||||
- Переводит `amount` с treasury → seller
|
||||
- Статус → released
|
||||
- Лог: `released: <id>`
|
||||
|
||||
```bash
|
||||
client call-contract --method release --arg "deal-001" \
|
||||
--contract $ESC_ID --key /tmp/buyer.json \
|
||||
--gas 20000 --node http://node1:8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `refund`
|
||||
|
||||
Seller добровольно возвращает деньги buyer'у.
|
||||
|
||||
**Аргументы:**
|
||||
| # | Имя | Тип |
|
||||
|---|-----|-----|
|
||||
| 0 | `id` | string |
|
||||
|
||||
**Права:** только seller.
|
||||
**Статус:** должен быть `active`.
|
||||
|
||||
**Поведение:**
|
||||
- Переводит `amount` с treasury → buyer
|
||||
- Статус → refunded
|
||||
- Лог: `refunded: <id>`
|
||||
|
||||
```bash
|
||||
client call-contract --method refund --arg "deal-001" \
|
||||
--contract $ESC_ID --key /tmp/seller.json \
|
||||
--gas 20000 --node http://node1:8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `dispute`
|
||||
|
||||
Открыть спор. Может вызвать buyer или seller.
|
||||
|
||||
**Аргументы:**
|
||||
| # | Имя | Тип |
|
||||
|---|-----|-----|
|
||||
| 0 | `id` | string |
|
||||
|
||||
**Статус:** должен быть `active`.
|
||||
|
||||
**Поведение:**
|
||||
- Статус → disputed
|
||||
- Лог: `disputed: <id>`
|
||||
- Средства остаются заблокированы в treasury
|
||||
|
||||
```bash
|
||||
client call-contract --method dispute --arg "deal-001" \
|
||||
--contract $ESC_ID --key /tmp/buyer.json \
|
||||
--gas 10000 --node http://node1:8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `resolve`
|
||||
|
||||
Admin разрешает спор.
|
||||
|
||||
**Аргументы:**
|
||||
| # | Имя | Тип | Значения |
|
||||
|---|-----|-----|---------|
|
||||
| 0 | `id` | string | ID эскроу |
|
||||
| 1 | `winner` | string | `buyer` или `seller` |
|
||||
|
||||
**Права:** только admin.
|
||||
**Статус:** должен быть `disputed`.
|
||||
|
||||
**Поведение:**
|
||||
- `winner=buyer` → treasury → buyer, статус → refunded
|
||||
- `winner=seller` → treasury → seller, статус → released
|
||||
- Лог: `resolved: <id>`
|
||||
|
||||
```bash
|
||||
# Admin решает в пользу buyer
|
||||
client call-contract --method resolve \
|
||||
--arg "deal-001" --arg buyer \
|
||||
--contract $ESC_ID --key /keys/node1.json \
|
||||
--gas 20000 --node http://node1:8080
|
||||
|
||||
# Admin решает в пользу seller
|
||||
client call-contract --method resolve \
|
||||
--arg "deal-001" --arg seller \
|
||||
--contract $ESC_ID --key /keys/node1.json \
|
||||
--gas 20000 --node http://node1:8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `info`
|
||||
|
||||
Запросить состояние эскроу.
|
||||
|
||||
**Аргументы:**
|
||||
| # | Имя | Тип |
|
||||
|---|-----|-----|
|
||||
| 0 | `id` | string |
|
||||
|
||||
**Логи:**
|
||||
```
|
||||
buyer: <pubkey>
|
||||
seller: <pubkey>
|
||||
amount: <µT>
|
||||
status: active|released|refunded|disputed
|
||||
```
|
||||
|
||||
```bash
|
||||
client call-contract --method info --arg "deal-001" \
|
||||
--contract $ESC_ID --key /keys/node1.json \
|
||||
--gas 5000 --node http://node1:8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## State Layout
|
||||
|
||||
```
|
||||
cstate:<contractID>:admin → admin pubkey
|
||||
cstate:<contractID>:e:<id>:b → buyer pubkey
|
||||
cstate:<contractID>:e:<id>:s → seller pubkey
|
||||
cstate:<contractID>:e:<id>:a → amount (uint64 big-endian)
|
||||
cstate:<contractID>:e:<id>:x → status byte ('a'=active, 'd'=disputed, 'r'=released, 'f'=refunded)
|
||||
```
|
||||
|
||||
## Полный сценарий с dispute
|
||||
|
||||
```bash
|
||||
# Параметры
|
||||
BUYER_KEY=/tmp/buyer.json
|
||||
SELLER_KEY=/tmp/seller.json
|
||||
ADMIN_KEY=/keys/node1.json
|
||||
ID="escrow-001"
|
||||
|
||||
# 1. Buyer создаёт эскроу на 5 T
|
||||
docker exec node1 client call-contract \
|
||||
--key $BUYER_KEY --contract $ESC_ID \
|
||||
--method create \
|
||||
--arg $ID --arg $SELLER_PUB --arg64 5000000 \
|
||||
--gas 30000 --node http://node1:8080
|
||||
|
||||
# 2. Проверить статус
|
||||
docker exec node1 client call-contract \
|
||||
--key $BUYER_KEY --contract $ESC_ID \
|
||||
--method info --arg $ID \
|
||||
--gas 5000 --node http://node1:8080
|
||||
# status: active
|
||||
|
||||
# 3. Buyer не доволен — открывает спор
|
||||
docker exec node1 client call-contract \
|
||||
--key $BUYER_KEY --contract $ESC_ID \
|
||||
--method dispute --arg $ID \
|
||||
--gas 10000 --node http://node1:8080
|
||||
|
||||
# 4. Admin рассматривает дело и решает в пользу seller
|
||||
docker exec node1 client call-contract \
|
||||
--key $ADMIN_KEY --contract $ESC_ID \
|
||||
--method resolve --arg $ID --arg seller \
|
||||
--gas 20000 --node http://node1:8080
|
||||
# Лог: resolved: escrow-001
|
||||
# Seller получает 5 T
|
||||
```
|
||||
203
docs/contracts/governance.md
Normal file
203
docs/contracts/governance.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# governance
|
||||
|
||||
On-chain governance контракт для управления параметрами сети. Deployer становится admin. Любой может предложить изменение параметра; admin принимает или отклоняет.
|
||||
|
||||
## Концепция
|
||||
|
||||
```
|
||||
Участник Admin
|
||||
│ │
|
||||
│── propose ──▶ │ state[prop:<key>] = value
|
||||
│ │
|
||||
│ │── approve ──▶ state[param:<key>] = value (live)
|
||||
│ │ удаляет prop:<key>
|
||||
│ │
|
||||
│ │── reject ──▶ удаляет prop:<key>
|
||||
```
|
||||
|
||||
После approve параметр становится **live** и читается нодой при следующем вызове контракта.
|
||||
|
||||
## Методы
|
||||
|
||||
### `init`
|
||||
|
||||
Инициализировать контракт, установить caller как admin.
|
||||
|
||||
**Аргументы:** нет
|
||||
|
||||
**Вызывается:** один раз после деплоя (deploy-скрипт делает это автоматически).
|
||||
|
||||
**Поведение:**
|
||||
- Устанавливает `state[admin] = caller_pubkey`
|
||||
- Идемпотентен: повторный вызов не меняет admin если уже установлен
|
||||
|
||||
```bash
|
||||
client call-contract --method init --contract $GOV_ID \
|
||||
--key /keys/node1.json --gas 5000 --node http://node1:8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `propose`
|
||||
|
||||
Предложить новое значение параметра. Доступно всем.
|
||||
|
||||
**Аргументы:**
|
||||
| # | Имя | Тип | Ограничение |
|
||||
|---|-----|-----|------------|
|
||||
| 0 | `key` | string | max 64 байта |
|
||||
| 1 | `value` | string | max 256 байт |
|
||||
|
||||
**Поведение:**
|
||||
- Сохраняет `state[prop:<key>] = value`
|
||||
- Лог: `proposed: <key>=<value>`
|
||||
- Если pending-предложение уже есть → перезаписывает
|
||||
|
||||
```bash
|
||||
# Предложить новую цену газа 5 µT/gas
|
||||
client call-contract --method propose \
|
||||
--arg gas_price --arg 5 \
|
||||
--contract $GOV_ID --key /keys/node1.json \
|
||||
--gas 10000 --node http://node1:8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `approve`
|
||||
|
||||
Admin принимает pending-предложение. Параметр становится live.
|
||||
|
||||
**Аргументы:**
|
||||
| # | Имя | Тип |
|
||||
|---|-----|-----|
|
||||
| 0 | `key` | string |
|
||||
|
||||
**Права:** только admin.
|
||||
|
||||
**Поведение:**
|
||||
- Копирует `state[prop:<key>]` → `state[param:<key>]`
|
||||
- Удаляет `state[prop:<key>]`
|
||||
- Лог: `approved: <key>`
|
||||
- Если нет pending → лог `no pending: <key>`
|
||||
|
||||
```bash
|
||||
client call-contract --method approve --arg gas_price \
|
||||
--contract $GOV_ID --key /keys/node1.json \
|
||||
--gas 10000 --node http://node1:8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `reject`
|
||||
|
||||
Admin отклоняет pending-предложение.
|
||||
|
||||
**Аргументы:**
|
||||
| # | Имя | Тип |
|
||||
|---|-----|-----|
|
||||
| 0 | `key` | string |
|
||||
|
||||
**Права:** только admin.
|
||||
|
||||
```bash
|
||||
client call-contract --method reject --arg gas_price \
|
||||
--contract $GOV_ID --key /keys/node1.json \
|
||||
--gas 5000 --node http://node1:8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `get`
|
||||
|
||||
Прочитать текущее live-значение параметра.
|
||||
|
||||
**Аргументы:**
|
||||
| # | Имя | Тип |
|
||||
|---|-----|-----|
|
||||
| 0 | `key` | string |
|
||||
|
||||
**Лог:**
|
||||
- `value: <value>` — параметр установлен
|
||||
- `not set: <key>` — параметр не установлен
|
||||
|
||||
```bash
|
||||
client call-contract --method get --arg gas_price \
|
||||
--contract $GOV_ID --key /keys/node1.json \
|
||||
--gas 3000 --node http://node1:8080
|
||||
```
|
||||
|
||||
Через REST (без транзакции):
|
||||
```bash
|
||||
curl http://localhost:8081/api/contracts/$GOV_ID/state/param:gas_price
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `get_pending`
|
||||
|
||||
Прочитать pending-предложение.
|
||||
|
||||
**Аргументы:**
|
||||
| # | Имя | Тип |
|
||||
|---|-----|-----|
|
||||
| 0 | `key` | string |
|
||||
|
||||
**Лог:**
|
||||
- `pending: <value>`
|
||||
- `no pending: <key>`
|
||||
|
||||
---
|
||||
|
||||
### `set_admin`
|
||||
|
||||
Передать роль admin другому адресу.
|
||||
|
||||
**Аргументы:**
|
||||
| # | Имя | Тип |
|
||||
|---|-----|-----|
|
||||
| 0 | `new_admin` | string (hex pubkey) |
|
||||
|
||||
**Права:** только текущий admin.
|
||||
|
||||
```bash
|
||||
client call-contract --method set_admin --arg <NEW_ADMIN_PUBKEY> \
|
||||
--contract $GOV_ID --key /keys/node1.json \
|
||||
--gas 10000 --node http://node1:8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Управляемые параметры
|
||||
|
||||
Governance хранит произвольные string-параметры. Нода читает следующие:
|
||||
|
||||
| Ключ | Тип | Описание | По умолчанию |
|
||||
|------|-----|---------|-------------|
|
||||
| `gas_price` | uint64 (строка) | µT за 1 gas unit | 1 µT |
|
||||
|
||||
> Нода читает `gas_price` при каждом `CALL_CONTRACT` через `chain.GetEffectiveGasPrice()`.
|
||||
> Изменение вступает в силу немедленно после `approve` — без перезапуска.
|
||||
|
||||
Можно хранить любые параметры приложения (messenger_entry_fee, relay_fee, etc.) и читать их из других контрактов через межконтрактные вызовы.
|
||||
|
||||
## State Layout
|
||||
|
||||
```
|
||||
cstate:<contractID>:admin → admin pubkey (hex string bytes)
|
||||
cstate:<contractID>:param:<key> → live value
|
||||
cstate:<contractID>:prop:<key> → pending proposal
|
||||
```
|
||||
|
||||
## Интеграция с нодой
|
||||
|
||||
После деплоя governance необходимо его **привязать** к ноде:
|
||||
|
||||
```bash
|
||||
# Автоматически через deploy-скрипт
|
||||
# Или вручную:
|
||||
curl -X POST http://localhost:8081/api/governance/link \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"governance\": \"$GOV_ID\"}"
|
||||
```
|
||||
|
||||
Подробнее: [Governance интеграция](../node/governance.md)
|
||||
133
docs/contracts/username_registry.md
Normal file
133
docs/contracts/username_registry.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# username_registry
|
||||
|
||||
Привязка читаемых `@username` к адресам (Ed25519 pubkey). Forward
|
||||
(`name → addr`) и reverse (`addr → name`) lookup, передача ownership,
|
||||
освобождение имени.
|
||||
|
||||
**Реализация: native Go** (`blockchain/native_username.go`). Работает
|
||||
без WASM VM — нулевая латентность, не может повесить AddBlock. Старая
|
||||
WASM-версия заменена; клиенты получают канонический ID через
|
||||
`/api/well-known-contracts`.
|
||||
|
||||
## Ключевые свойства
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Contract ID** | `native:username_registry` |
|
||||
| **Version** | `2.1.0-native` |
|
||||
| **Регистрация** | Flat fee 10 000 µT (0.01 T), **burn** — никому не идёт |
|
||||
| **Ограничения имени** | 4–32 символа, `a-z 0-9 _ -`, первый символ — буква |
|
||||
| **Зарезервированные** | `system`, `admin`, `root`, `dchain`, `null`, `none` |
|
||||
| **Один адрес — одно имя** | Чтобы сменить, нужно `release` + `register` |
|
||||
|
||||
Комиссия 10 000 µT выбрана как баланс: достаточно дёшево для реальных
|
||||
пользователей, достаточно дорого чтобы один ID-squatter не забил тысячу
|
||||
имён из скучающего бота.
|
||||
|
||||
## Оплата
|
||||
|
||||
Плата передаётся в `tx.Amount` (видна в истории транзакций), контракт
|
||||
проверяет её точное значение и вычитает из баланса отправителя. Не
|
||||
направляется никуда — токены сгорают.
|
||||
|
||||
Дополнительно платится `tx.Fee = 1 000 µT` за саму транзакцию — это
|
||||
network fee валидатору, стандартный `MinFee` для любой tx.
|
||||
|
||||
Итого: `register(name)` стоит **11 000 µT ≈ 0.011 T**.
|
||||
|
||||
## Методы
|
||||
|
||||
### `register(name)` — payable 10000
|
||||
|
||||
Зарегистрировать имя для `tx.From`.
|
||||
|
||||
**Проверки** (в порядке):
|
||||
1. `name` валиден (длина, символы, не reserved)
|
||||
2. `tx.Amount == 10 000` (ошибка `register requires tx.amount = 10000 µT`)
|
||||
3. Имя не занято
|
||||
4. `tx.From` не владеет другим именем
|
||||
5. Баланс отправителя достаточен для `amount + fee + gas`
|
||||
|
||||
**Успех:**
|
||||
- Списывает 10 000 µT с баланса (burn)
|
||||
- `cstate[name:<name>] = tx.From`
|
||||
- `cstate[addr:<tx.From>] = name`
|
||||
- Лог `registered: <name> → <pubkey>`
|
||||
|
||||
**CLI-пример:**
|
||||
```bash
|
||||
client call-contract \
|
||||
--key my.json \
|
||||
--contract native:username_registry \
|
||||
--method register \
|
||||
--args '["alice"]' \
|
||||
--amount 10000 \
|
||||
--node http://localhost:8081
|
||||
```
|
||||
|
||||
**Клиент-приложение** делает это автоматически через Settings →
|
||||
«Купить никнейм».
|
||||
|
||||
### `resolve(name)` — free
|
||||
|
||||
Прочитать владельца имени.
|
||||
|
||||
Логирует `owner: <pubkey>` или `not found: <name>`. Клиент в HTTP-API
|
||||
обычно использует прямое чтение state:
|
||||
```
|
||||
GET /api/contracts/native:username_registry/state/name:alice
|
||||
→ { value_hex: "<hex of owner pubkey ASCII>" }
|
||||
```
|
||||
|
||||
### `lookup(address)` — free
|
||||
|
||||
Обратный lookup. Логирует `name: <name>` или `no name: <address>`.
|
||||
Клиент использует `GET .../state/addr:<pubkey>`.
|
||||
|
||||
### `transfer(name, new_owner)` — free
|
||||
|
||||
Передать `name` другому адресу. Только текущий владелец может вызвать.
|
||||
Новый владелец не должен уже иметь имя.
|
||||
|
||||
### `release(name)` — free
|
||||
|
||||
Удалить привязку. Только текущий владелец. Освобождает `name` и
|
||||
`addr:<caller>` для будущих регистраций.
|
||||
|
||||
## State layout
|
||||
|
||||
Все ключи записываются в общий namespace `cstate:native:username_registry:`:
|
||||
|
||||
| Key | Value |
|
||||
|-----|-------|
|
||||
| `name:<name>` | Hex pubkey владельца (64 символа) |
|
||||
| `addr:<pubkey>` | ASCII-строка с именем |
|
||||
|
||||
Значения хранятся как байты; HTTP endpoint
|
||||
`/api/contracts/:id/state/:key` возвращает их в `value_hex`, клиент
|
||||
делает `hex → UTF-8` перед показом.
|
||||
|
||||
## Клиентская интеграция
|
||||
|
||||
- `useWellKnownContracts` хук авто-синхронизирует `settings.contractId =
|
||||
native:username_registry`
|
||||
- `resolveUsername(contractId, name)` и `reverseResolve(contractId, addr)`
|
||||
в `client-app/lib/api.ts` делают HTTP-запрос + hex-decode
|
||||
- Settings экран покупает никнейм через `buildCallContractTx({amount:
|
||||
10_000, ...})` + автоматический `reverseResolve` polling после submit
|
||||
для показа `@name` в профиле за ~1 блок
|
||||
|
||||
## Отличия от предыдущей WASM-версии
|
||||
|
||||
| | WASM v1 | Native v2 |
|
||||
|---|---------|-----------|
|
||||
| **Fee** | 10^(7-len), tiered | Flat 10 000 µT |
|
||||
| **Fee destination** | Contract treasury | Burn |
|
||||
| **Min length** | 1 | **4** |
|
||||
| **Payment point** | Debited inside contract, invisible in tx | `tx.Amount`, visible in history |
|
||||
| **Latency** | ~10 ms (wazero startup) | ~50 µs (direct Go call) |
|
||||
| **VM hang risk** | Да (требует timeout) | Нет |
|
||||
|
||||
Зарегистрированные в старой WASM-версии имена не мигрируются
|
||||
автоматически — операторам нужно пере-зарегистрировать после reset
|
||||
chain (см. `CHANGELOG.md` «Compatibility notes»).
|
||||
Reference in New Issue
Block a user