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
6.2 KiB
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 может быть только:
dc.Caller()— списать со счёта вызывающего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.