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
194 lines
6.2 KiB
Markdown
194 lines
6.2 KiB
Markdown
# Gas и Treasury
|
||
|
||
## Gas модель
|
||
|
||
Каждый вызов контракта (`CALL_CONTRACT` транзакция) тратит gas. Gas конвертируется в µT и списывается со счёта отправителя.
|
||
|
||
### Формула
|
||
|
||
```
|
||
fee = gas_used × gas_price
|
||
```
|
||
|
||
| Параметр | Значение | Откуда |
|
||
|---------|---------|-------|
|
||
| `gas_used` | ≤ `--gas` лимит | считается VM |
|
||
| `gas_price` | 1 µT/gas по умолчанию | governance или константа |
|
||
|
||
### Лимит gas
|
||
|
||
Задаётся в транзакции флагом `--gas`:
|
||
|
||
```bash
|
||
client call-contract --method increment \
|
||
--gas 5000 \
|
||
--contract $CONTRACT_ID --key key.json --node http://localhost:8081
|
||
```
|
||
|
||
Если VM израсходует весь лимит до конца исполнения — транзакция откатывается, gas не возвращается.
|
||
|
||
### Что стоит gas
|
||
|
||
| Операция | Стоимость |
|
||
|---------|---------|
|
||
| Итерация цикла (`gas_tick`) | 1 unit |
|
||
| Внешний вызов (`call_contract`) | gas подвызова |
|
||
| Прочие инструкции | 0 (не инструментированы) |
|
||
|
||
> TinyGo автоматически вставляет `gas_tick` в циклы. Бинарные WASM-контракты вызывают `gas_tick` вручную.
|
||
|
||
### Межконтрактный gas
|
||
|
||
При вызове `dc.CallContract(...)`, подвызов получает budget = `Remaining()` родителя.
|
||
После возврата, `gasUsed` подвызова списывается с родительского счётчика.
|
||
|
||
```
|
||
Родитель: limit=5000, used=200
|
||
→ подвызов budget = 4800
|
||
→ подвызов использовал 300
|
||
Родитель: used = 200 + 300 = 500
|
||
```
|
||
|
||
## Treasury
|
||
|
||
Каждый контракт имеет **treasury** — специальный баланс, привязанный к контракту.
|
||
|
||
### Получить адрес treasury
|
||
|
||
```go
|
||
treasury := dc.Treasury() // hex pubkey, 64 символа
|
||
```
|
||
|
||
### Переводы через treasury
|
||
|
||
Treasury используется как промежуточный эскроу:
|
||
|
||
```go
|
||
// Принять деньги от caller → treasury
|
||
dc.Transfer(dc.Caller(), dc.Treasury(), amount)
|
||
|
||
// Отправить деньги из treasury → получателю
|
||
dc.Transfer(dc.Treasury(), recipient, amount)
|
||
```
|
||
|
||
**Ограничение:** в `dc.Transfer(from, ...)`, `from` может быть только:
|
||
1. `dc.Caller()` — списать со счёта вызывающего
|
||
2. `dc.Treasury()` — списать с treasury контракта
|
||
|
||
Контракт **не может** списывать деньги с произвольных адресов.
|
||
|
||
### Проверить баланс treasury
|
||
|
||
```go
|
||
treasuryBal := dc.Balance(dc.Treasury())
|
||
```
|
||
|
||
### Паттерны использования treasury
|
||
|
||
**1. Fee collector:**
|
||
```go
|
||
const fee = 1000 // µT
|
||
dc.Transfer(dc.Caller(), dc.Treasury(), fee)
|
||
// Деньги остаются в treasury навсегда (или до явного вывода)
|
||
```
|
||
|
||
**2. Эскроу (lock → release):**
|
||
```go
|
||
// lock: buyer → treasury
|
||
dc.Transfer(buyer, dc.Treasury(), amount)
|
||
|
||
// release: treasury → seller
|
||
dc.Transfer(dc.Treasury(), seller, amount)
|
||
|
||
// refund: treasury → buyer
|
||
dc.Transfer(dc.Treasury(), buyer, amount)
|
||
```
|
||
|
||
**3. Prize pool (аукцион):**
|
||
```go
|
||
// Принять ставку
|
||
dc.Transfer(bidder, dc.Treasury(), bid)
|
||
|
||
// Возврат предыдущей ставки
|
||
dc.Transfer(dc.Treasury(), prevBidder, prevBid)
|
||
|
||
// Финал: перевод победителю
|
||
dc.Transfer(dc.Treasury(), seller, topBid)
|
||
```
|
||
|
||
## Governance и динамический gas_price
|
||
|
||
По умолчанию `gas_price = 1 µT/gas`. Это значение можно изменить через governance-контракт.
|
||
|
||
### Как нода читает gas_price
|
||
|
||
```go
|
||
// blockchain/chain.go
|
||
func (c *Chain) GetEffectiveGasPrice() uint64 {
|
||
val, ok := c.GetGovParam("gas_price")
|
||
if !ok { return GasPrice } // константа по умолчанию
|
||
price, err := strconv.ParseUint(val, 10, 64)
|
||
if err != nil { return GasPrice }
|
||
return price
|
||
}
|
||
```
|
||
|
||
`GetGovParam` читает `cstate:<govID>:param:gas_price` напрямую из BadgerDB без VM-вызова.
|
||
|
||
### Изменить gas_price через governance
|
||
|
||
```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
|
||
|
||
# Admin утверждает
|
||
client call-contract --method approve \
|
||
--arg gas_price \
|
||
--contract $GOV_ID --key /keys/node1.json \
|
||
--gas 10000 --node http://node1:8080
|
||
```
|
||
|
||
После approve все последующие `CALL_CONTRACT` транзакции будут использовать новую цену.
|
||
|
||
### Проверить текущий gas_price
|
||
|
||
```bash
|
||
# Через контракт
|
||
client call-contract --method get \
|
||
--arg gas_price \
|
||
--contract $GOV_ID --key key.json \
|
||
--gas 3000 --node http://localhost:8081
|
||
|
||
# Через REST (без транзакции)
|
||
curl http://localhost:8081/api/contracts/$GOV_ID/state/param:gas_price
|
||
```
|
||
|
||
## Константы
|
||
|
||
```go
|
||
// blockchain/chain.go
|
||
const (
|
||
GasPrice = uint64(1) // µT за 1 gas unit (default)
|
||
)
|
||
|
||
// vm/host.go
|
||
const (
|
||
maxContractCallDepth = 8
|
||
)
|
||
```
|
||
|
||
## Рекомендации по выбору --gas
|
||
|
||
| Операция | Рекомендуемый gas |
|
||
|---------|-----------------|
|
||
| Простое чтение/запись state | 3 000 |
|
||
| Регистрация / короткий метод | 5 000 |
|
||
| Метод с несколькими state операциями | 10 000 |
|
||
| Создание эскроу/аукциона | 20 000 – 30 000 |
|
||
| Метод с межконтрактным вызовом | 30 000 – 50 000 |
|
||
|
||
Gas, который не был потрачён, **не возвращается**. Завышенный лимит безопасен, но лишних денег не списывается больше `gas_used × gas_price`.
|