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:
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`.
|
||||
Reference in New Issue
Block a user