Files
dchain/docs/development/binary-wasm.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.9 KiB
Raw Permalink Blame History

Бинарный WASM

Альтернативный способ написания контрактов — генерация WASM-байткода вручную. Используется для системных/минимальных контрактов где критичен размер (~5002000 байт вместо 30100 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).

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

Вспомогательные функции

// Секция с длиной-префиксом
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 Убрать верхний элемент стека

Шаблон генератора

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-кода:

func increment() {
    count := getU64("counter")
    putU64("counter", count+1)
    log("incremented")
}

В бинарном WASM:

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

Сборка и деплой

# Генерация
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.