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

7.7 KiB
Raw Blame History

TinyGo SDK

Руководство по написанию DChain smart contracts на Go с использованием TinyGo.

Установка TinyGo

# 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 или коммитим?)

Минимальный контракт:

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"

Аргументы

// Строковый аргумент по индексу (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

// Читать/писать произвольные байты
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)        // записать

Идентификация

caller := dc.Caller()         // hex pubkey вызывающего (64 символа)
height := dc.BlockHeight()    // uint64, текущий блок
treasury := dc.Treasury()     // hex адрес treasury контракта

Токены

// Баланс адреса в µT
bal := dc.Balance(pubkey)                  // uint64

// Перевод µT
ok := dc.Transfer(from, to, amount)        // bool: true=success

Transfer в контексте контракта: from должен быть либо caller'ом, либо dc.Treasury() — только так контракт может тратить чужие деньги.

Межконтрактные вызовы

// Вызвать метод другого контракта
ok := dc.CallContract(contractID, "method", `["arg1","arg2"]`)

// Без аргументов
ok := dc.CallContract(contractID, "get_price", "[]")

Подробнее: Межконтрактные вызовы

Логирование

dc.Log("message")               // строка видна в Explorer → Logs
dc.Log("value: " + strconv.FormatUint(v, 10))

Паттерны

Owner-только метод

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

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

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

Сборка

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)

Деплой

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 тесте
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.

Демонстрирует:

  • increment / get / reset — счётчик с owner ACL
  • greet — строковые аргументы
  • ping — межконтрактный вызов
# Собрать
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