Files
dchain/docs/development/tinygo.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

288 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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