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
138 lines
3.9 KiB
Go
138 lines
3.9 KiB
Go
// Counter smart contract — compiles to WASM with GOOS=wasip1 GOARCH=wasm.
|
|
//
|
|
// Methods (exported via //go:export):
|
|
// - increment — adds 1 to the stored counter
|
|
// - get — logs the current value (readable via /api/contracts/{id}/state/counter)
|
|
// - reset — resets counter to 0; only the first caller (owner) is allowed
|
|
//
|
|
// Host imports from the "env" module (see vm/host.go):
|
|
// - put_u64(keyPtr, keyLen, val) — stores uint64 as 8-byte big-endian
|
|
// - get_u64(keyPtr, keyLen) uint64 — reads 8-byte big-endian uint64
|
|
// - get_caller(buf, bufLen) int32 — writes caller pub key hex into buf
|
|
// - get_state(kPtr,kLen,dPtr,dLen) int32 — reads raw state bytes
|
|
// - set_state(kPtr,kLen,vPtr,vLen) — writes raw state bytes
|
|
// - log(msgPtr, msgLen) — emits message to node log
|
|
//
|
|
//go:build wasip1
|
|
|
|
package main
|
|
|
|
import (
|
|
"unsafe"
|
|
)
|
|
|
|
// ── host function imports ─────────────────────────────────────────────────────
|
|
|
|
//go:wasmimport env put_u64
|
|
func hostPutU64(keyPtr unsafe.Pointer, keyLen int32, val uint64)
|
|
|
|
//go:wasmimport env get_u64
|
|
func hostGetU64(keyPtr unsafe.Pointer, keyLen int32) uint64
|
|
|
|
//go:wasmimport env get_caller
|
|
func hostGetCaller(buf unsafe.Pointer, bufLen int32) int32
|
|
|
|
//go:wasmimport env get_state
|
|
func hostGetState(keyPtr unsafe.Pointer, keyLen int32, dstPtr unsafe.Pointer, dstLen int32) int32
|
|
|
|
//go:wasmimport env set_state
|
|
func hostSetState(keyPtr unsafe.Pointer, keyLen int32, valPtr unsafe.Pointer, valLen int32)
|
|
|
|
//go:wasmimport env log
|
|
func hostLog(msgPtr unsafe.Pointer, msgLen int32)
|
|
|
|
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
|
|
func logMsg(s string) {
|
|
if len(s) == 0 {
|
|
return
|
|
}
|
|
b := []byte(s)
|
|
hostLog(unsafe.Pointer(&b[0]), int32(len(b)))
|
|
}
|
|
|
|
func putU64(key string, val uint64) {
|
|
b := []byte(key)
|
|
hostPutU64(unsafe.Pointer(&b[0]), int32(len(b)), val)
|
|
}
|
|
|
|
func getU64(key string) uint64 {
|
|
b := []byte(key)
|
|
return hostGetU64(unsafe.Pointer(&b[0]), int32(len(b)))
|
|
}
|
|
|
|
func getState(key string, dst []byte) int32 {
|
|
kb := []byte(key)
|
|
return hostGetState(unsafe.Pointer(&kb[0]), int32(len(kb)),
|
|
unsafe.Pointer(&dst[0]), int32(len(dst)))
|
|
}
|
|
|
|
func setState(key string, val []byte) {
|
|
kb := []byte(key)
|
|
hostSetState(unsafe.Pointer(&kb[0]), int32(len(kb)),
|
|
unsafe.Pointer(&val[0]), int32(len(val)))
|
|
}
|
|
|
|
func getCaller() string {
|
|
buf := make([]byte, 128)
|
|
n := hostGetCaller(unsafe.Pointer(&buf[0]), int32(len(buf)))
|
|
if n <= 0 {
|
|
return ""
|
|
}
|
|
return string(buf[:n])
|
|
}
|
|
|
|
// ── contract state keys ───────────────────────────────────────────────────────
|
|
|
|
const (
|
|
keyCounter = "counter"
|
|
keyOwner = "owner"
|
|
)
|
|
|
|
// ── exported contract methods ─────────────────────────────────────────────────
|
|
|
|
//go:export increment
|
|
func increment() {
|
|
val := getU64(keyCounter)
|
|
val++
|
|
putU64(keyCounter, val)
|
|
logMsg("incremented")
|
|
}
|
|
|
|
//go:export get
|
|
func get() {
|
|
logMsg("get called")
|
|
}
|
|
|
|
//go:export reset
|
|
func reset() {
|
|
caller := getCaller()
|
|
if caller == "" {
|
|
logMsg("reset: no caller")
|
|
return
|
|
}
|
|
|
|
ownerBuf := make([]byte, 128)
|
|
ownerLen := getState(keyOwner, ownerBuf)
|
|
|
|
if ownerLen == 0 {
|
|
// No owner set yet — first caller becomes the owner.
|
|
setState(keyOwner, []byte(caller))
|
|
putU64(keyCounter, 0)
|
|
logMsg("reset ok (owner set)")
|
|
return
|
|
}
|
|
|
|
owner := string(ownerBuf[:ownerLen])
|
|
if caller != owner {
|
|
logMsg("reset: unauthorized")
|
|
return
|
|
}
|
|
|
|
putU64(keyCounter, 0)
|
|
logMsg("reset ok")
|
|
}
|
|
|
|
// main is required by the Go runtime for wasip1 programs.
|
|
func main() {}
|