Files
dchain/docs/development/gas-model.md
vsecoder 7e7393e4f8 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
2026-04-17 14:16:44 +03:00

6.2 KiB
Raw Blame History

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:

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

treasury := dc.Treasury()   // hex pubkey, 64 символа

Переводы через treasury

Treasury используется как промежуточный эскроу:

// Принять деньги от 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

treasuryBal := dc.Balance(dc.Treasury())

Паттерны использования treasury

1. Fee collector:

const fee = 1000 // µT
dc.Transfer(dc.Caller(), dc.Treasury(), fee)
// Деньги остаются в treasury навсегда (или до явного вывода)

2. Эскроу (lock → release):

// 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 (аукцион):

// Принять ставку
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

// 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

# Предложить новое значение (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

# Через контракт
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

Константы

// 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.