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:
103
docs/development/README.md
Normal file
103
docs/development/README.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# Разработка контрактов
|
||||
|
||||
DChain поддерживает два способа написания WASM-контрактов.
|
||||
|
||||
## Выбор подхода
|
||||
|
||||
| | TinyGo SDK | Бинарный WASM |
|
||||
|-|-----------|--------------|
|
||||
| **Язык** | Go | Go (кодогенератор) |
|
||||
| **Инструменты** | TinyGo 0.30+ | Стандартный Go |
|
||||
| **Сложность** | Низкая | Высокая |
|
||||
| **Размер .wasm** | ~30–100 KB | 500–2000 байт |
|
||||
| **Отладка** | Стандартная | Сложная |
|
||||
| **Рекомендуется** | Новые контракты | Минимальные/системные |
|
||||
|
||||
## TinyGo SDK (рекомендуется)
|
||||
|
||||
Пишите контракты как обычный Go-код:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import dc "go-blockchain/contracts/sdk"
|
||||
|
||||
//export increment
|
||||
func increment() {
|
||||
v := dc.GetU64("counter")
|
||||
dc.PutU64("counter", v+1)
|
||||
dc.Log("incremented")
|
||||
}
|
||||
|
||||
func main() {}
|
||||
```
|
||||
|
||||
Подробное руководство: [TinyGo SDK](tinygo.md)
|
||||
|
||||
## Бинарный WASM
|
||||
|
||||
Генераторы в `contracts/*/gen/main.go` создают минимальные WASM-модули вручную через LEB128-кодирование. Размер получается ~500 байт, но написание сложное.
|
||||
|
||||
Подробнее: [Бинарный WASM](binary-wasm.md)
|
||||
|
||||
## Документация по разделам
|
||||
|
||||
| Документ | Содержание |
|
||||
|---------|-----------|
|
||||
| [TinyGo SDK](tinygo.md) | Установка, SDK API, сборка, деплой, пример |
|
||||
| [Host functions](host-functions.md) | Полный справочник 14 host-функций |
|
||||
| [Межконтрактные вызовы](inter-contract.md) | call_contract, composability, глубина |
|
||||
| [Бинарный WASM](binary-wasm.md) | LEB128, секции, кодогенератор |
|
||||
| [Gas и Treasury](gas-model.md) | Gas модель, treasury, governance |
|
||||
|
||||
## Общая структура контракта
|
||||
|
||||
```
|
||||
contracts/
|
||||
mycontract/
|
||||
main.go # TinyGo источник
|
||||
mycontract_abi.json # ABI (описание методов)
|
||||
mycontract.wasm # Скомпилированный бинарник
|
||||
```
|
||||
|
||||
## ABI формат
|
||||
|
||||
```json
|
||||
{
|
||||
"contract": "mycontract",
|
||||
"version": "1.0.0",
|
||||
"description": "...",
|
||||
"methods": [
|
||||
{
|
||||
"name": "method_name",
|
||||
"description": "...",
|
||||
"args": [
|
||||
{"name": "param1", "type": "string"},
|
||||
{"name": "amount", "type": "uint64"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Поддерживаемые типы: `string`, `uint64`, `bytes`.
|
||||
|
||||
## Деплой контракта
|
||||
|
||||
```bash
|
||||
# Локально
|
||||
client deploy-contract \
|
||||
--key key.json \
|
||||
--wasm mycontract.wasm \
|
||||
--abi mycontract_abi.json \
|
||||
--node http://localhost:8081
|
||||
|
||||
# В Docker
|
||||
docker exec node1 client deploy-contract \
|
||||
--key /keys/node1.json \
|
||||
--wasm /path/to/mycontract.wasm \
|
||||
--abi /path/to/mycontract_abi.json \
|
||||
--node http://node1:8080
|
||||
```
|
||||
|
||||
Успешный деплой логирует `contract_id: <hex16>`.
|
||||
249
docs/development/binary-wasm.md
Normal file
249
docs/development/binary-wasm.md
Normal file
@@ -0,0 +1,249 @@
|
||||
# Бинарный WASM
|
||||
|
||||
Альтернативный способ написания контрактов — генерация WASM-байткода вручную. Используется для системных/минимальных контрактов где критичен размер (~500–2000 байт вместо 30–100 KB TinyGo).
|
||||
|
||||
> **Рекомендуется TinyGo** для новых контрактов. Бинарный WASM используется для встроенных контрактов в этом проекте.
|
||||
|
||||
## Структура генератора
|
||||
|
||||
```
|
||||
contracts/
|
||||
mycontract/
|
||||
gen/
|
||||
main.go # Go-программа, печатает WASM байты
|
||||
mycontract.wasm # Результат: go run gen/main.go > mycontract.wasm
|
||||
mycontract_abi.json
|
||||
```
|
||||
|
||||
Генератор запускается стандартным Go (`go run gen/main.go`) и выводит бинарный WASM в stdout.
|
||||
|
||||
## Анатомия WASM модуля
|
||||
|
||||
```
|
||||
Magic + Version : \0asm\x01\x00\x00\x00
|
||||
|
||||
Секции (по порядку):
|
||||
1. Type — сигнатуры функций
|
||||
2. Import — импортируемые host-функции ("env" модуль)
|
||||
3. Function — индексы типов для локальных функций
|
||||
4. Export — экспортируемые функции (методы контракта)
|
||||
5. Code — тела функций
|
||||
6. Data — статические строки в памяти
|
||||
7. Memory — объявление памяти (мин. 1 страница = 64 KB)
|
||||
```
|
||||
|
||||
## LEB128 кодирование
|
||||
|
||||
Целые числа в WASM кодируются в LEB128 (variable-length encoding).
|
||||
|
||||
```go
|
||||
// Unsigned LEB128
|
||||
func u(v uint64) []byte {
|
||||
var out []byte
|
||||
for {
|
||||
b := byte(v & 0x7f)
|
||||
v >>= 7
|
||||
if v != 0 {
|
||||
b |= 0x80
|
||||
}
|
||||
out = append(out, b)
|
||||
if v == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Signed LEB128 (для i32/i64 константы)
|
||||
func s(v int64) []byte {
|
||||
var out []byte
|
||||
for {
|
||||
b := byte(v & 0x7f)
|
||||
v >>= 7
|
||||
if (v == 0 && b&0x40 == 0) || (v == -1 && b&0x40 != 0) {
|
||||
out = append(out, b)
|
||||
break
|
||||
}
|
||||
out = append(out, b|0x80)
|
||||
}
|
||||
return out
|
||||
}
|
||||
```
|
||||
|
||||
## Вспомогательные функции
|
||||
|
||||
```go
|
||||
// Секция с длиной-префиксом
|
||||
func section(id byte, content []byte) []byte {
|
||||
return append(append([]byte{id}, u(uint64(len(content)))...), content...)
|
||||
}
|
||||
|
||||
// Вектор (count + элементы)
|
||||
func vec(items ...[]byte) []byte {
|
||||
out := u(uint64(len(items)))
|
||||
for _, item := range items {
|
||||
out = append(out, item...)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Строка с длиной-префиксом
|
||||
func str(s string) []byte {
|
||||
return append(u(uint64(len(s))), []byte(s)...)
|
||||
}
|
||||
```
|
||||
|
||||
## WASM инструкции
|
||||
|
||||
Наиболее используемые опкоды:
|
||||
|
||||
| Инструкция | Байт | Описание |
|
||||
|-----------|------|---------|
|
||||
| `local.get` | `0x20 + leb(idx)` | Прочитать локальную переменную |
|
||||
| `local.set` | `0x21 + leb(idx)` | Записать локальную переменную |
|
||||
| `local.tee` | `0x22 + leb(idx)` | Записать и оставить на стеке |
|
||||
| `i32.const` | `0x41 + sleb(val)` | Константа i32 |
|
||||
| `i64.const` | `0x42 + sleb(val)` | Константа i64 |
|
||||
| `i32.load` | `0x28 0x02 leb(offset)` | Загрузить 4 байта |
|
||||
| `i32.store` | `0x36 0x02 leb(offset)` | Сохранить 4 байта |
|
||||
| `i64.load` | `0x29 0x03 leb(offset)` | Загрузить 8 байт |
|
||||
| `i64.store` | `0x37 0x03 leb(offset)` | Сохранить 8 байт |
|
||||
| `i32.add` | `0x6A` | Сложение i32 |
|
||||
| `i32.sub` | `0x6B` | Вычитание i32 |
|
||||
| `i32.eq` | `0x46` | Равенство i32 |
|
||||
| `i64.eq` | `0x51` | Равенство i64 |
|
||||
| `if` | `0x04 0x40` | Ветвление (void) |
|
||||
| `else` | `0x05` | — |
|
||||
| `end` | `0x0B` | Конец блока/функции |
|
||||
| `return` | `0x0F` | Возврат из функции |
|
||||
| `call` | `0x10 + leb(funcIdx)` | Вызов функции |
|
||||
| `drop` | `0x1A` | Убрать верхний элемент стека |
|
||||
|
||||
## Шаблон генератора
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// Импорты host-функций
|
||||
const (
|
||||
fnGetArgStr = 0 // индекс в таблице импортов
|
||||
fnSetState = 1
|
||||
fnGetState = 2
|
||||
fnGetStateLen = 3
|
||||
fnGetCaller = 4
|
||||
fnLog = 5
|
||||
)
|
||||
|
||||
// Смещения в памяти
|
||||
const (
|
||||
pKey = 0 // буфер для ключей state
|
||||
pVal = 128 // буфер для значений
|
||||
pCaller = 256 // буфер для caller
|
||||
pArg0 = 320 // буфер для аргумента 0
|
||||
pStatic = 400 // статические строки (из Data секции)
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 1. Magic + version
|
||||
wasm := []byte{0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00}
|
||||
|
||||
// 2. Type секция — сигнатуры функций
|
||||
// ...
|
||||
|
||||
// 3. Import секция — host функции
|
||||
// ...
|
||||
|
||||
// 4. Function + Memory + Export + Code + Data
|
||||
// ...
|
||||
|
||||
os.Stdout.Write(wasm)
|
||||
}
|
||||
```
|
||||
|
||||
## Пример: функция increment
|
||||
|
||||
Аналог Go-кода:
|
||||
```go
|
||||
func increment() {
|
||||
count := getU64("counter")
|
||||
putU64("counter", count+1)
|
||||
log("incremented")
|
||||
}
|
||||
```
|
||||
|
||||
В бинарном WASM:
|
||||
```go
|
||||
// В Data секции: "counter\x00" @ offset 0, "incremented\x00" @ offset 8
|
||||
// Функция increment:
|
||||
func buildIncrement() []byte {
|
||||
// locals: none
|
||||
body := []byte{0x00} // local decl count = 0
|
||||
|
||||
// load key "counter" (ptr=0, len=7) → call get_u64 → result on stack
|
||||
body = append(body, 0x41) // i32.const
|
||||
body = append(body, s(0)...) // ptr = 0 (offset of "counter" in data)
|
||||
body = append(body, 0x41)
|
||||
body = append(body, s(7)...) // len = 7
|
||||
body = append(body, 0x10) // call
|
||||
body = append(body, u(fnGetU64)...)
|
||||
|
||||
// add 1
|
||||
body = append(body, 0x42, 0x01) // i64.const 1
|
||||
body = append(body, 0x7C) // i64.add
|
||||
|
||||
// put_u64("counter", result)
|
||||
body = append(body, 0x21, 0x00) // local.set 0 (tmp)
|
||||
body = append(body, 0x41)
|
||||
body = append(body, s(0)...)
|
||||
body = append(body, 0x41)
|
||||
body = append(body, s(7)...)
|
||||
body = append(body, 0x20, 0x00) // local.get 0
|
||||
body = append(body, 0x10)
|
||||
body = append(body, u(fnPutU64)...)
|
||||
|
||||
// log("incremented")
|
||||
body = append(body, 0x41)
|
||||
body = append(body, s(8)...) // ptr = 8 (offset of "incremented")
|
||||
body = append(body, 0x41)
|
||||
body = append(body, s(11)...) // len = 11
|
||||
body = append(body, 0x10)
|
||||
body = append(body, u(fnLog)...)
|
||||
|
||||
body = append(body, 0x0B) // end
|
||||
return append(u(uint64(len(body))), body...)
|
||||
}
|
||||
```
|
||||
|
||||
## Сборка и деплой
|
||||
|
||||
```bash
|
||||
# Генерация
|
||||
go run contracts/mycontract/gen/main.go > contracts/mycontract/mycontract.wasm
|
||||
|
||||
# Проверка (wasm-objdump из wabt)
|
||||
wasm-objdump -d contracts/mycontract/mycontract.wasm
|
||||
|
||||
# Деплой
|
||||
client deploy-contract \
|
||||
--key key.json \
|
||||
--wasm contracts/mycontract/mycontract.wasm \
|
||||
--abi contracts/mycontract/mycontract_abi.json \
|
||||
--node http://localhost:8081
|
||||
```
|
||||
|
||||
## Встроенные контракты в проекте
|
||||
|
||||
| Контракт | Генератор | Размер .wasm |
|
||||
|---------|----------|-------------|
|
||||
| counter | contracts/counter/gen/main.go | ~500 байт |
|
||||
| governance | contracts/governance/gen/main.go | ~800 байт |
|
||||
| name_registry | contracts/name_registry/gen/main.go | ~1.2 KB |
|
||||
| username_registry | contracts/username_registry/gen/main.go | ~1.5 KB |
|
||||
| escrow | contracts/escrow/gen/main.go | ~2 KB |
|
||||
| auction | contracts/auction/gen/main.go | ~2.5 KB |
|
||||
|
||||
Все генераторы используют одни и те же LEB128-утилиты и паттерн `vec/section/str`.
|
||||
193
docs/development/gas-model.md
Normal file
193
docs/development/gas-model.md
Normal file
@@ -0,0 +1,193 @@
|
||||
# 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`.
|
||||
274
docs/development/host-functions.md
Normal file
274
docs/development/host-functions.md
Normal file
@@ -0,0 +1,274 @@
|
||||
# Host Functions
|
||||
|
||||
Полный справочник 14 host-функций, которые DChain экспортирует в WASM-контракты через модуль `env`.
|
||||
|
||||
Все функции регистрируются в `vm/host.go`. Контракт импортирует их через `//go:wasmimport env <name>` (TinyGo) или напрямую в секции `imports` WASM-модуля.
|
||||
|
||||
---
|
||||
|
||||
## Аргументы
|
||||
|
||||
### `get_args`
|
||||
|
||||
```
|
||||
get_args(dstPtr i32, dstLen i32) → written i32
|
||||
```
|
||||
|
||||
Читает весь JSON-массив аргументов транзакции в буфер `[dstPtr, dstPtr+dstLen)`.
|
||||
Возвращает количество записанных байт (0 если аргументов нет).
|
||||
|
||||
---
|
||||
|
||||
### `get_arg_str`
|
||||
|
||||
```
|
||||
get_arg_str(idx i32, dstPtr i32, dstLen i32) → written i32
|
||||
```
|
||||
|
||||
Читает аргумент с индексом `idx` как строку (без кавычек JSON).
|
||||
`dstLen` — максимальная длина буфера.
|
||||
Возвращает 0 если аргумент не существует или тип не строка.
|
||||
|
||||
**SDK:** `dc.ArgStr(idx, maxLen)`
|
||||
|
||||
---
|
||||
|
||||
### `get_arg_u64`
|
||||
|
||||
```
|
||||
get_arg_u64(idx i32) → val i64
|
||||
```
|
||||
|
||||
Читает аргумент с индексом `idx` как беззнаковое 64-битное число.
|
||||
Возвращает 0 если индекс вне диапазона или тип не число.
|
||||
|
||||
**SDK:** `dc.ArgU64(idx)`
|
||||
|
||||
---
|
||||
|
||||
## State
|
||||
|
||||
### `get_state_len`
|
||||
|
||||
```
|
||||
get_state_len(keyPtr i32, keyLen i32) → valLen i32
|
||||
```
|
||||
|
||||
Возвращает размер значения по ключу (0 если ключ не найден).
|
||||
Используется перед `get_state` для выделения буфера нужного размера.
|
||||
|
||||
---
|
||||
|
||||
### `get_state`
|
||||
|
||||
```
|
||||
get_state(keyPtr i32, keyLen i32, dstPtr i32, dstLen i32) → written i32
|
||||
```
|
||||
|
||||
Читает `min(len(value), dstLen)` байт по ключу в буфер.
|
||||
Возвращает фактически записанное количество байт.
|
||||
|
||||
**SDK:** `dc.GetState(key)` (выделяет буфер автоматически через `get_state_len`)
|
||||
|
||||
---
|
||||
|
||||
### `set_state`
|
||||
|
||||
```
|
||||
set_state(keyPtr i32, keyLen i32, valPtr i32, valLen i32)
|
||||
```
|
||||
|
||||
Записывает значение по ключу. Если `valLen == 0` — удаляет ключ.
|
||||
|
||||
**SDK:** `dc.SetState(key, value)`, `dc.SetStateStr(key, s)`, `dc.PutU64(key, v)`
|
||||
|
||||
---
|
||||
|
||||
### `put_u64`
|
||||
|
||||
```
|
||||
put_u64(keyPtr i32, keyLen i32, val i64)
|
||||
```
|
||||
|
||||
Записывает `val` как 8 байт big-endian. Эквивалентно `set_state` с 8-байтным big-endian значением, но компактнее в вызове.
|
||||
|
||||
**SDK:** `dc.PutU64(key, v)`
|
||||
|
||||
---
|
||||
|
||||
### `get_u64`
|
||||
|
||||
```
|
||||
get_u64(keyPtr i32, keyLen i32) → val i64
|
||||
```
|
||||
|
||||
Читает 8 байт big-endian по ключу. Возвращает 0 если ключ не найден.
|
||||
|
||||
**SDK:** `dc.GetU64(key)`
|
||||
|
||||
---
|
||||
|
||||
## Идентификация
|
||||
|
||||
### `get_caller`
|
||||
|
||||
```
|
||||
get_caller(bufPtr i32, bufLen i32) → written i32
|
||||
```
|
||||
|
||||
Записывает hex-pubkey вызывающего аккаунта (64 символа ASCII).
|
||||
Если `bufLen < 64`, записывает частично.
|
||||
|
||||
**SDK:** `dc.Caller()`
|
||||
|
||||
---
|
||||
|
||||
### `get_block_height`
|
||||
|
||||
```
|
||||
get_block_height() → height i64
|
||||
```
|
||||
|
||||
Возвращает высоту текущего блока (в котором исполняется транзакция).
|
||||
|
||||
**SDK:** `dc.BlockHeight()`
|
||||
|
||||
---
|
||||
|
||||
### `get_contract_treasury`
|
||||
|
||||
```
|
||||
get_contract_treasury(bufPtr i32, bufLen i32) → written i32
|
||||
```
|
||||
|
||||
Записывает hex-pubkey treasury контракта (64 символа).
|
||||
Treasury — специальный счёт, привязанный к контракту. Контракт может переводить с treasury как `from` без ограничений.
|
||||
|
||||
**SDK:** `dc.Treasury()`
|
||||
|
||||
---
|
||||
|
||||
## Токены
|
||||
|
||||
### `get_balance`
|
||||
|
||||
```
|
||||
get_balance(pubPtr i32, pubLen i32) → balance i64
|
||||
```
|
||||
|
||||
Возвращает баланс адреса в µT (микро-токены).
|
||||
|
||||
**SDK:** `dc.Balance(pubKeyHex)`
|
||||
|
||||
---
|
||||
|
||||
### `transfer`
|
||||
|
||||
```
|
||||
transfer(fromPtr i32, fromLen i32, toPtr i32, toLen i32, amount i64) → errCode i32
|
||||
```
|
||||
|
||||
Переводит `amount` µT с `from` на `to`.
|
||||
Возвращает 0 при успехе, 1 при ошибке (недостаточно средств, неверный адрес).
|
||||
|
||||
**Ограничения:**
|
||||
- `from` должен быть либо `dc.Caller()`, либо `dc.Treasury()`.
|
||||
- Контракт не может тратить чужие деньги (только caller'а или свой treasury).
|
||||
|
||||
**SDK:** `dc.Transfer(from, to, amount) bool`
|
||||
|
||||
---
|
||||
|
||||
## Межконтрактные вызовы
|
||||
|
||||
### `call_contract`
|
||||
|
||||
```
|
||||
call_contract(cidPtr i32, cidLen i32, mthPtr i32, mthLen i32, argPtr i32, argLen i32) → errCode i32
|
||||
```
|
||||
|
||||
Вызывает метод другого контракта.
|
||||
|
||||
| Параметр | Тип | Описание |
|
||||
|---------|-----|---------|
|
||||
| `cidPtr/cidLen` | i32 | hex-ID целевого контракта (16 символов) |
|
||||
| `mthPtr/mthLen` | i32 | имя метода |
|
||||
| `argPtr/argLen` | i32 | JSON-массив аргументов |
|
||||
|
||||
**Возврат:** 0 = успех, 1 = ошибка (превышена глубина, контракт не найден, и т.д.)
|
||||
|
||||
**Gas:** подвызов получает `gc.Remaining()` от родительского счётчика. Потраченный gas вычитается из родителя.
|
||||
|
||||
**State:** все вызовы в цепочке разделяют одну `badger.Txn`. Если подвызов не падает, его изменения видны родителю.
|
||||
|
||||
**Подробнее:** [Межконтрактные вызовы](inter-contract.md)
|
||||
|
||||
**SDK:** `dc.CallContract(contractID, method, argsJSON) bool`
|
||||
|
||||
---
|
||||
|
||||
## Логирование
|
||||
|
||||
### `log`
|
||||
|
||||
```
|
||||
log(msgPtr i32, msgLen i32)
|
||||
```
|
||||
|
||||
Записывает строку в лог контракта. Логи привязаны к транзакции и видны в Explorer → вкладка Logs.
|
||||
|
||||
`fmt.Println` и `log.Printf` через WASI stdout **не** попадают в блокчейн-логи. Используйте только `log`.
|
||||
|
||||
**SDK:** `dc.Log(msg)`
|
||||
|
||||
---
|
||||
|
||||
## Gas
|
||||
|
||||
### `gas_tick`
|
||||
|
||||
```
|
||||
gas_tick()
|
||||
```
|
||||
|
||||
Вызывается автоматически при каждой итерации цикла в бинарных WASM-контрактах (инструментация вручную). Списывает 1 unit gas.
|
||||
|
||||
Для TinyGo-контрактов TinyGo генерирует собственную инструментацию.
|
||||
|
||||
---
|
||||
|
||||
## Таблица функций
|
||||
|
||||
| # | Имя | Сигнатура | SDK |
|
||||
|---|-----|----------|-----|
|
||||
| 1 | `get_args` | `(i32,i32)→i32` | — |
|
||||
| 2 | `get_arg_str` | `(i32,i32,i32)→i32` | `ArgStr` |
|
||||
| 3 | `get_arg_u64` | `(i32)→i64` | `ArgU64` |
|
||||
| 4 | `get_state_len` | `(i32,i32)→i32` | внутри `GetState` |
|
||||
| 5 | `get_state` | `(i32,i32,i32,i32)→i32` | `GetState` |
|
||||
| 6 | `set_state` | `(i32,i32,i32,i32)` | `SetState` |
|
||||
| 7 | `put_u64` | `(i32,i32,i64)` | `PutU64` |
|
||||
| 8 | `get_u64` | `(i32,i32)→i64` | `GetU64` |
|
||||
| 9 | `get_caller` | `(i32,i32)→i32` | `Caller` |
|
||||
| 10 | `get_block_height` | `()→i64` | `BlockHeight` |
|
||||
| 11 | `get_contract_treasury` | `(i32,i32)→i32` | `Treasury` |
|
||||
| 12 | `get_balance` | `(i32,i32)→i64` | `Balance` |
|
||||
| 13 | `transfer` | `(i32,i32,i32,i32,i64)→i32` | `Transfer` |
|
||||
| 14 | `call_contract` | `(i32,i32,i32,i32,i32,i32)→i32` | `CallContract` |
|
||||
| 15 | `log` | `(i32,i32)` | `Log` |
|
||||
| 16 | `gas_tick` | `()` | — |
|
||||
|
||||
---
|
||||
|
||||
## Паттерн прямого импорта (без SDK)
|
||||
|
||||
Если вы пишете бинарный WASM вручную (без TinyGo), функции импортируются в секции `imports`:
|
||||
|
||||
```
|
||||
(import "env" "get_state_len" (func (param i32 i32) (result i32)))
|
||||
(import "env" "get_state" (func (param i32 i32 i32 i32) (result i32)))
|
||||
(import "env" "set_state" (func (param i32 i32 i32 i32)))
|
||||
...
|
||||
```
|
||||
|
||||
Подробнее: [Бинарный WASM](binary-wasm.md)
|
||||
129
docs/development/inter-contract.md
Normal file
129
docs/development/inter-contract.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# Межконтрактные вызовы
|
||||
|
||||
DChain поддерживает вызовы одного контракта из другого через host-функцию `call_contract`. Все вызовы в цепочке исполняются в рамках одной транзакции и одного `badger.Txn`.
|
||||
|
||||
## Синтаксис (TinyGo SDK)
|
||||
|
||||
```go
|
||||
import dc "go-blockchain/contracts/sdk"
|
||||
|
||||
// Вызвать метод с аргументами
|
||||
ok := dc.CallContract(contractID, "method_name", `["arg1","arg2"]`)
|
||||
|
||||
// Без аргументов
|
||||
ok := dc.CallContract(contractID, "get_price", "[]")
|
||||
|
||||
// С числовым аргументом
|
||||
ok := dc.CallContract(contractID, "increment", `[42]`)
|
||||
```
|
||||
|
||||
`contractID` — 16-символьный hex-ID контракта (как в Explorer).
|
||||
|
||||
## Как это работает
|
||||
|
||||
```
|
||||
tx CALL_CONTRACT → Contract A
|
||||
│
|
||||
│ call_contract(B, "method", args)
|
||||
▼
|
||||
Contract B
|
||||
│
|
||||
│ call_contract(C, "method", args)
|
||||
▼
|
||||
Contract C
|
||||
│
|
||||
└── [возврат gasUsed]
|
||||
│
|
||||
[gasUsed заряжается на B]
|
||||
[возврат gasUsed]
|
||||
│
|
||||
[gasUsed заряжается на A]
|
||||
```
|
||||
|
||||
### Атомарность
|
||||
|
||||
Все изменения состояния в цепочке вызовов используют **одну** `badger.Txn`. Если родительский вызов завершился ошибкой, изменения всех подвызовов откатываются вместе с ним. Если подвызов вернул 1 (ошибку), родитель должен сам решить: паниковать или продолжать.
|
||||
|
||||
### Gas
|
||||
|
||||
Gas-бюджет подвызова = `gc.Remaining()` родителя (оставшийся gas на момент вызова). После возврата, `gasUsed` подвызова списывается с родительского счётчика. Таким образом, весь gas всей цепочки вычитается из единственного `--gas` лимита транзакции.
|
||||
|
||||
### Caller
|
||||
|
||||
В подвызове `dc.Caller()` возвращает contractID **вызывающего контракта** (не исходного пользователя).
|
||||
|
||||
```
|
||||
user → Contract A → Contract B
|
||||
dc.Caller() == Contract A's ID
|
||||
```
|
||||
|
||||
### Глубина
|
||||
|
||||
Максимальная глубина вложенности: **8**.
|
||||
`A → B → C → ... → H` (8 уровней) — допустимо.
|
||||
Попытка вызвать 9-й уровень вернёт ошибку, транзакция откатится.
|
||||
|
||||
## Пример: price oracle
|
||||
|
||||
```go
|
||||
// contracts/mycontract/main.go
|
||||
package main
|
||||
|
||||
import dc "go-blockchain/contracts/sdk"
|
||||
|
||||
const priceOracleID = "abcd1234abcd1234" // ID oracle-контракта
|
||||
|
||||
//export buy
|
||||
func buy() {
|
||||
amount := dc.ArgU64(0)
|
||||
|
||||
// Узнать цену у другого контракта
|
||||
if !dc.CallContract(priceOracleID, "get_price", "[]") {
|
||||
dc.Log("oracle unavailable")
|
||||
return
|
||||
}
|
||||
// После вызова цена может быть записана в shared state под известным ключом,
|
||||
// или oracle мог сделать transfer напрямую.
|
||||
|
||||
dc.Log("buy executed")
|
||||
}
|
||||
|
||||
func main() {}
|
||||
```
|
||||
|
||||
## Пример: ping (hello_go)
|
||||
|
||||
```go
|
||||
//export ping
|
||||
func ping() {
|
||||
target := dc.ArgStr(0, 64)
|
||||
if target == "" {
|
||||
dc.Log("no target")
|
||||
return
|
||||
}
|
||||
ok := dc.CallContract(target, "get", "[]")
|
||||
if ok {
|
||||
dc.Log("ping ok")
|
||||
} else {
|
||||
dc.Log("ping failed")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Ограничения
|
||||
|
||||
| Параметр | Значение |
|
||||
|---------|---------|
|
||||
| Максимальная глубина | 8 |
|
||||
| Разделяемый state | одна `badger.Txn` |
|
||||
| Caller в подвызове | contractID родителя |
|
||||
| Gas бюджет подвызова | `Remaining()` родителя |
|
||||
| Возврат данных | только через state (нет return value) |
|
||||
|
||||
> **Нет возврата значений.** `call_contract` возвращает только 0/1 (успех/ошибка). Для передачи данных из подвызова обратно: запишите в state под известным ключом или используйте промежуточный общий контракт.
|
||||
|
||||
## Безопасность
|
||||
|
||||
- Не доверяйте `dc.Caller()` из подвызванного контракта — он будет contractID инициатора, который может быть любым задеплоенным контрактом.
|
||||
- Проверяйте contractID перед вызовом, если вам важно с кем вы говорите.
|
||||
- Помните что подвызов может изменять общий state — не вызывайте непроверенные контракты.
|
||||
287
docs/development/tinygo.md
Normal file
287
docs/development/tinygo.md
Normal file
@@ -0,0 +1,287 @@
|
||||
# TinyGo SDK
|
||||
|
||||
Руководство по написанию DChain smart contracts на Go с использованием TinyGo.
|
||||
|
||||
## Установка TinyGo
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
brew install tinygo
|
||||
|
||||
# Linux (deb)
|
||||
wget https://github.com/tinygo-org/tinygo/releases/download/v0.32.0/tinygo_0.32.0_amd64.deb
|
||||
sudo dpkg -i tinygo_0.32.0_amd64.deb
|
||||
|
||||
# Windows
|
||||
# Скачать installer с https://github.com/tinygo-org/tinygo/releases
|
||||
|
||||
# Проверка
|
||||
tinygo version
|
||||
```
|
||||
|
||||
Требуется TinyGo **0.30+** (поддержка `//go:wasmimport` + target wasip1).
|
||||
|
||||
## Структура контракта
|
||||
|
||||
```
|
||||
contracts/
|
||||
mycontract/
|
||||
main.go # контракт
|
||||
mycontract_abi.json # ABI
|
||||
mycontract.wasm # собранный (gitignore или коммитим?)
|
||||
```
|
||||
|
||||
Минимальный контракт:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import dc "go-blockchain/contracts/sdk"
|
||||
|
||||
//export my_method
|
||||
func myMethod() {
|
||||
dc.Log("hello from Go contract!")
|
||||
}
|
||||
|
||||
// main обязателен для TinyGo, но не вызывается.
|
||||
func main() {}
|
||||
```
|
||||
|
||||
Каждая `//export <name>` функция становится вызываемым методом контракта.
|
||||
|
||||
## SDK API
|
||||
|
||||
Импорт: `import dc "go-blockchain/contracts/sdk"`
|
||||
|
||||
### Аргументы
|
||||
|
||||
```go
|
||||
// Строковый аргумент по индексу (maxLen — максимальная длина буфера)
|
||||
name := dc.ArgStr(0, 64) // args[0] as string
|
||||
addr := dc.ArgStr(1, 128) // args[1] as string
|
||||
|
||||
// Числовой аргумент (uint64)
|
||||
amount := dc.ArgU64(0) // args[0] as uint64
|
||||
```
|
||||
|
||||
### State
|
||||
|
||||
```go
|
||||
// Читать/писать произвольные байты
|
||||
data := dc.GetState("key") // []byte или nil
|
||||
dc.SetState("key", []byte("value")) // записать
|
||||
dc.SetState("key", nil) // удалить
|
||||
|
||||
// Удобные string обёртки
|
||||
s := dc.GetStateStr("owner") // string (пустой если нет)
|
||||
dc.SetStateStr("owner", caller) // записать string
|
||||
|
||||
// uint64 (хранится как 8-byte big-endian)
|
||||
count := dc.GetU64("counter") // uint64
|
||||
dc.PutU64("counter", count+1) // записать
|
||||
```
|
||||
|
||||
### Идентификация
|
||||
|
||||
```go
|
||||
caller := dc.Caller() // hex pubkey вызывающего (64 символа)
|
||||
height := dc.BlockHeight() // uint64, текущий блок
|
||||
treasury := dc.Treasury() // hex адрес treasury контракта
|
||||
```
|
||||
|
||||
### Токены
|
||||
|
||||
```go
|
||||
// Баланс адреса в µT
|
||||
bal := dc.Balance(pubkey) // uint64
|
||||
|
||||
// Перевод µT
|
||||
ok := dc.Transfer(from, to, amount) // bool: true=success
|
||||
```
|
||||
|
||||
`Transfer` в контексте контракта: `from` должен быть либо caller'ом, либо `dc.Treasury()` — только так контракт может тратить чужие деньги.
|
||||
|
||||
### Межконтрактные вызовы
|
||||
|
||||
```go
|
||||
// Вызвать метод другого контракта
|
||||
ok := dc.CallContract(contractID, "method", `["arg1","arg2"]`)
|
||||
|
||||
// Без аргументов
|
||||
ok := dc.CallContract(contractID, "get_price", "[]")
|
||||
```
|
||||
|
||||
Подробнее: [Межконтрактные вызовы](inter-contract.md)
|
||||
|
||||
### Логирование
|
||||
|
||||
```go
|
||||
dc.Log("message") // строка видна в Explorer → Logs
|
||||
dc.Log("value: " + strconv.FormatUint(v, 10))
|
||||
```
|
||||
|
||||
## Паттерны
|
||||
|
||||
### Owner-только метод
|
||||
|
||||
```go
|
||||
//export admin_action
|
||||
func adminAction() {
|
||||
owner := dc.GetStateStr("owner")
|
||||
if owner == "" {
|
||||
// первый вызов — устанавливаем owner
|
||||
dc.SetStateStr("owner", dc.Caller())
|
||||
dc.Log("initialized")
|
||||
return
|
||||
}
|
||||
if dc.Caller() != owner {
|
||||
dc.Log("unauthorized")
|
||||
return
|
||||
}
|
||||
// ... логика ...
|
||||
}
|
||||
```
|
||||
|
||||
### Платный метод (fee в treasury)
|
||||
|
||||
```go
|
||||
//export register
|
||||
func register() {
|
||||
name := dc.ArgStr(0, 64)
|
||||
if name == "" { return }
|
||||
|
||||
const fee = 1000 // µT
|
||||
caller := dc.Caller()
|
||||
treasury := dc.Treasury()
|
||||
|
||||
// Снять fee с caller и положить в treasury
|
||||
if !dc.Transfer(caller, treasury, fee) {
|
||||
dc.Log("insufficient balance")
|
||||
return
|
||||
}
|
||||
dc.SetStateStr("reg:"+name, caller)
|
||||
dc.Log("registered: " + name)
|
||||
}
|
||||
```
|
||||
|
||||
### Эскроу с release
|
||||
|
||||
```go
|
||||
//export lock
|
||||
func lock() {
|
||||
id := dc.ArgStr(0, 64)
|
||||
amount := dc.ArgU64(1)
|
||||
buyer := dc.Caller()
|
||||
treasury := dc.Treasury()
|
||||
|
||||
if !dc.Transfer(buyer, treasury, amount) {
|
||||
dc.Log("transfer failed")
|
||||
return
|
||||
}
|
||||
dc.SetStateStr("buyer:"+id, buyer)
|
||||
dc.PutU64("amount:"+id, amount)
|
||||
dc.Log("locked: " + id)
|
||||
}
|
||||
|
||||
//export release
|
||||
func release() {
|
||||
id := dc.ArgStr(0, 64)
|
||||
buyer := dc.GetStateStr("buyer:" + id)
|
||||
if dc.Caller() != buyer {
|
||||
dc.Log("unauthorized")
|
||||
return
|
||||
}
|
||||
seller := dc.ArgStr(1, 128)
|
||||
amount := dc.GetU64("amount:" + id)
|
||||
dc.Transfer(dc.Treasury(), seller, amount)
|
||||
dc.Log("released: " + id)
|
||||
}
|
||||
```
|
||||
|
||||
## Сборка
|
||||
|
||||
```bash
|
||||
cd contracts/mycontract
|
||||
|
||||
# Собрать WASM
|
||||
tinygo build -o mycontract.wasm -target wasip1 -no-debug .
|
||||
|
||||
# Проверить размер
|
||||
ls -lh mycontract.wasm
|
||||
|
||||
# Опционально: wasm-strip для уменьшения размера
|
||||
wasm-strip mycontract.wasm
|
||||
```
|
||||
|
||||
Флаги:
|
||||
- `-target wasip1` — WASI Preview 1 (единственный поддерживаемый target)
|
||||
- `-no-debug` — убрать debug info из WASM (уменьшает размер)
|
||||
- `-opt=2` — агрессивная оптимизация (по умолчанию в release)
|
||||
|
||||
## Деплой
|
||||
|
||||
```bash
|
||||
CONTRACT_ID=$(client deploy-contract \
|
||||
--key /keys/node1.json \
|
||||
--wasm mycontract.wasm \
|
||||
--abi mycontract_abi.json \
|
||||
--node http://localhost:8081 | grep contract_id | awk '{print $2}')
|
||||
|
||||
echo "Contract: $CONTRACT_ID"
|
||||
```
|
||||
|
||||
## Проверка WASM
|
||||
|
||||
Перед деплоем можно проверить что модуль валиден:
|
||||
|
||||
```go
|
||||
// В Go тесте
|
||||
ctx := context.Background()
|
||||
v := vm.NewVM(ctx)
|
||||
defer v.Close(ctx)
|
||||
|
||||
wasmBytes, _ := os.ReadFile("mycontract.wasm")
|
||||
if err := v.Validate(ctx, wasmBytes); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
## Пример: hello_go
|
||||
|
||||
Полный пример TinyGo-контракта с комментариями находится в [`contracts/hello_go/main.go`](../../contracts/hello_go/main.go).
|
||||
|
||||
Демонстрирует:
|
||||
- `increment` / `get` / `reset` — счётчик с owner ACL
|
||||
- `greet` — строковые аргументы
|
||||
- `ping` — межконтрактный вызов
|
||||
|
||||
```bash
|
||||
# Собрать
|
||||
cd contracts/hello_go
|
||||
tinygo build -o hello_go.wasm -target wasip1 -no-debug .
|
||||
|
||||
# Задеплоить
|
||||
docker exec node1 client deploy-contract \
|
||||
--key /keys/node1.json \
|
||||
--wasm hello_go.wasm \
|
||||
--abi hello_go_abi.json \
|
||||
--node http://node1:8080
|
||||
|
||||
# Вызвать
|
||||
docker exec node1 client call-contract \
|
||||
--key /keys/node1.json --contract $HELLO_ID \
|
||||
--method increment --gas 5000 --node http://node1:8080
|
||||
|
||||
docker exec node1 client call-contract \
|
||||
--key /keys/node1.json --contract $HELLO_ID \
|
||||
--method greet --arg "DChain" --gas 5000 --node http://node1:8080
|
||||
```
|
||||
|
||||
## Ограничения TinyGo в контекcте WASM
|
||||
|
||||
- Нет `goroutine` в WASM (нет многопоточности)
|
||||
- Нет `net`, `os`, `syscall` — контракт изолирован
|
||||
- Нет доступа к файловой системе и сети
|
||||
- `fmt.Println` и `log.Printf` работают через WASI stdout — **но не логируются в blockchain**.
|
||||
Используйте только `dc.Log()` для логов видимых в Explorer.
|
||||
- Размер памяти по умолчанию: 64 KB на stack + heap управляется TinyGo GC
|
||||
Reference in New Issue
Block a user