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

250 lines
7.9 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.

# Бинарный 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).
```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`.