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:
210
contracts/sdk/dchain.go
Normal file
210
contracts/sdk/dchain.go
Normal file
@@ -0,0 +1,210 @@
|
||||
//go:build tinygo
|
||||
|
||||
// Package dchain is the DChain smart contract SDK for TinyGo.
|
||||
//
|
||||
// # Build a contract
|
||||
//
|
||||
// tinygo build -o mycontract.wasm -target wasip1 -no-debug ./mycontract
|
||||
//
|
||||
// # Deploy
|
||||
//
|
||||
// client deploy-contract --key key.json \
|
||||
// --wasm mycontract.wasm --abi mycontract_abi.json \
|
||||
// --node http://localhost:8081
|
||||
//
|
||||
// Each exported Go function becomes a callable contract method.
|
||||
// All inputs come through Arg*/GetState; all outputs go through Log/SetState.
|
||||
package dchain
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// ── Argument accessors ────────────────────────────────────────────────────────
|
||||
|
||||
//go:wasmimport env get_arg_str
|
||||
func hostGetArgStr(idx uint32, ptr uintptr, maxLen uint32) uint32
|
||||
|
||||
//go:wasmimport env get_arg_u64
|
||||
func hostGetArgU64(idx uint32) uint64
|
||||
|
||||
// ArgStr returns the idx-th call argument as a string (max maxLen bytes).
|
||||
// Returns "" if the index is out of range.
|
||||
func ArgStr(idx int, maxLen int) string {
|
||||
buf := make([]byte, maxLen)
|
||||
n := hostGetArgStr(uint32(idx), uintptr(unsafe.Pointer(&buf[0])), uint32(maxLen))
|
||||
if n == 0 {
|
||||
return ""
|
||||
}
|
||||
return string(buf[:n])
|
||||
}
|
||||
|
||||
// ArgU64 returns the idx-th call argument as a uint64. Returns 0 if out of range.
|
||||
func ArgU64(idx int) uint64 {
|
||||
return hostGetArgU64(uint32(idx))
|
||||
}
|
||||
|
||||
// ── State ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
//go:wasmimport env get_state
|
||||
func hostGetState(kPtr uintptr, kLen uint32, dstPtr uintptr, dstLen uint32) uint32
|
||||
|
||||
//go:wasmimport env set_state
|
||||
func hostSetState(kPtr uintptr, kLen uint32, vPtr uintptr, vLen uint32)
|
||||
|
||||
//go:wasmimport env get_u64
|
||||
func hostGetU64(kPtr uintptr, kLen uint32) uint64
|
||||
|
||||
//go:wasmimport env put_u64
|
||||
func hostPutU64(kPtr uintptr, kLen uint32, val uint64)
|
||||
|
||||
// GetState reads a value from contract state by key.
|
||||
// Returns nil if the key does not exist.
|
||||
func GetState(key string) []byte {
|
||||
k := []byte(key)
|
||||
buf := make([]byte, 1024)
|
||||
n := hostGetState(
|
||||
uintptr(unsafe.Pointer(&k[0])), uint32(len(k)),
|
||||
uintptr(unsafe.Pointer(&buf[0])), uint32(len(buf)),
|
||||
)
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
return buf[:n]
|
||||
}
|
||||
|
||||
// GetStateStr reads a contract state value as a string.
|
||||
func GetStateStr(key string) string {
|
||||
v := GetState(key)
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
return string(v)
|
||||
}
|
||||
|
||||
// SetState writes a value to contract state.
|
||||
// Passing an empty slice clears the key.
|
||||
func SetState(key string, value []byte) {
|
||||
k := []byte(key)
|
||||
if len(value) == 0 {
|
||||
// vPtr=0, vLen=0 → clear key
|
||||
hostSetState(uintptr(unsafe.Pointer(&k[0])), uint32(len(k)), 0, 0)
|
||||
return
|
||||
}
|
||||
hostSetState(
|
||||
uintptr(unsafe.Pointer(&k[0])), uint32(len(k)),
|
||||
uintptr(unsafe.Pointer(&value[0])), uint32(len(value)),
|
||||
)
|
||||
}
|
||||
|
||||
// SetStateStr writes a string value to contract state.
|
||||
func SetStateStr(key, value string) {
|
||||
SetState(key, []byte(value))
|
||||
}
|
||||
|
||||
// GetU64 reads a uint64 stored by PutU64 from contract state.
|
||||
func GetU64(key string) uint64 {
|
||||
k := []byte(key)
|
||||
return hostGetU64(uintptr(unsafe.Pointer(&k[0])), uint32(len(k)))
|
||||
}
|
||||
|
||||
// PutU64 stores a uint64 in contract state as 8-byte big-endian.
|
||||
func PutU64(key string, val uint64) {
|
||||
k := []byte(key)
|
||||
hostPutU64(uintptr(unsafe.Pointer(&k[0])), uint32(len(k)), val)
|
||||
}
|
||||
|
||||
// ── Caller & chain ────────────────────────────────────────────────────────────
|
||||
|
||||
//go:wasmimport env get_caller
|
||||
func hostGetCaller(bufPtr uintptr, bufLen uint32) uint32
|
||||
|
||||
//go:wasmimport env get_block_height
|
||||
func hostGetBlockHeight() uint64
|
||||
|
||||
//go:wasmimport env get_contract_treasury
|
||||
func hostGetContractTreasury(bufPtr uintptr, bufLen uint32) uint32
|
||||
|
||||
// Caller returns the hex pubkey of the transaction sender (or parent contract ID).
|
||||
func Caller() string {
|
||||
buf := make([]byte, 128)
|
||||
n := hostGetCaller(uintptr(unsafe.Pointer(&buf[0])), uint32(len(buf)))
|
||||
return string(buf[:n])
|
||||
}
|
||||
|
||||
// BlockHeight returns the height of the block currently being processed.
|
||||
func BlockHeight() uint64 {
|
||||
return hostGetBlockHeight()
|
||||
}
|
||||
|
||||
// Treasury returns the contract's ownerless escrow address.
|
||||
// Derived as hex(sha256(contractID+":treasury")); no private key exists.
|
||||
// Only this contract can spend from it via Transfer.
|
||||
func Treasury() string {
|
||||
buf := make([]byte, 64)
|
||||
n := hostGetContractTreasury(uintptr(unsafe.Pointer(&buf[0])), uint32(len(buf)))
|
||||
return string(buf[:n])
|
||||
}
|
||||
|
||||
// ── Token operations ──────────────────────────────────────────────────────────
|
||||
|
||||
//go:wasmimport env get_balance
|
||||
func hostGetBalance(pubPtr uintptr, pubLen uint32) int64
|
||||
|
||||
//go:wasmimport env transfer
|
||||
func hostTransfer(fromPtr uintptr, fromLen uint32, toPtr uintptr, toLen uint32, amount uint64) uint32
|
||||
|
||||
// Balance returns the token balance of a hex pubkey address in µT.
|
||||
func Balance(pubKey string) uint64 {
|
||||
p := []byte(pubKey)
|
||||
return uint64(hostGetBalance(uintptr(unsafe.Pointer(&p[0])), uint32(len(p))))
|
||||
}
|
||||
|
||||
// Transfer sends amount µT from one address to another.
|
||||
// Returns true on success, false if from has insufficient balance.
|
||||
func Transfer(from, to string, amount uint64) bool {
|
||||
f := []byte(from)
|
||||
t := []byte(to)
|
||||
return hostTransfer(
|
||||
uintptr(unsafe.Pointer(&f[0])), uint32(len(f)),
|
||||
uintptr(unsafe.Pointer(&t[0])), uint32(len(t)),
|
||||
amount,
|
||||
) == 0
|
||||
}
|
||||
|
||||
// ── Inter-contract calls ──────────────────────────────────────────────────────
|
||||
|
||||
//go:wasmimport env call_contract
|
||||
func hostCallContract(cidPtr uintptr, cidLen uint32, mthPtr uintptr, mthLen uint32, argPtr uintptr, argLen uint32) uint32
|
||||
|
||||
// CallContract executes a method on another deployed contract.
|
||||
// argsJSON must be a JSON array, e.g. `["alice", "100"]`.
|
||||
// Caller of the sub-contract is set to this contract's ID.
|
||||
// Gas is shared — sub-call consumes from the parent's gas budget.
|
||||
// Returns true on success.
|
||||
func CallContract(contractID, method, argsJSON string) bool {
|
||||
cid := []byte(contractID)
|
||||
mth := []byte(method)
|
||||
if argsJSON == "" {
|
||||
argsJSON = "[]"
|
||||
}
|
||||
arg := []byte(argsJSON)
|
||||
return hostCallContract(
|
||||
uintptr(unsafe.Pointer(&cid[0])), uint32(len(cid)),
|
||||
uintptr(unsafe.Pointer(&mth[0])), uint32(len(mth)),
|
||||
uintptr(unsafe.Pointer(&arg[0])), uint32(len(arg)),
|
||||
) == 0
|
||||
}
|
||||
|
||||
// ── Logging ───────────────────────────────────────────────────────────────────
|
||||
|
||||
//go:wasmimport env log
|
||||
func hostLog(msgPtr uintptr, msgLen uint32)
|
||||
|
||||
// Log writes a message to the contract log.
|
||||
// Logs are visible in the block explorer at /contract?id=<id> → Logs tab.
|
||||
func Log(msg string) {
|
||||
b := []byte(msg)
|
||||
if len(b) == 0 {
|
||||
return
|
||||
}
|
||||
hostLog(uintptr(unsafe.Pointer(&b[0])), uint32(len(b)))
|
||||
}
|
||||
53
contracts/sdk/dchain_stub.go
Normal file
53
contracts/sdk/dchain_stub.go
Normal file
@@ -0,0 +1,53 @@
|
||||
//go:build !tinygo
|
||||
|
||||
// Package dchain provides stub implementations for non-TinyGo builds.
|
||||
// These allow go build / IDEs to compile contract code without TinyGo.
|
||||
// The stubs panic at runtime — they are never executed in production.
|
||||
package dchain
|
||||
|
||||
// ArgStr returns the idx-th call argument as a string.
|
||||
func ArgStr(idx int, maxLen int) string { panic("dchain: ArgStr requires TinyGo (tinygo build -target wasip1)") }
|
||||
|
||||
// ArgU64 returns the idx-th call argument as a uint64.
|
||||
func ArgU64(idx int) uint64 { panic("dchain: ArgU64 requires TinyGo") }
|
||||
|
||||
// GetState reads a value from contract state.
|
||||
func GetState(key string) []byte { panic("dchain: GetState requires TinyGo") }
|
||||
|
||||
// GetStateStr reads a contract state value as a string.
|
||||
func GetStateStr(key string) string { panic("dchain: GetStateStr requires TinyGo") }
|
||||
|
||||
// SetState writes a value to contract state.
|
||||
func SetState(key string, value []byte) { panic("dchain: SetState requires TinyGo") }
|
||||
|
||||
// SetStateStr writes a string value to contract state.
|
||||
func SetStateStr(key, value string) { panic("dchain: SetStateStr requires TinyGo") }
|
||||
|
||||
// GetU64 reads a uint64 from contract state.
|
||||
func GetU64(key string) uint64 { panic("dchain: GetU64 requires TinyGo") }
|
||||
|
||||
// PutU64 stores a uint64 in contract state.
|
||||
func PutU64(key string, val uint64) { panic("dchain: PutU64 requires TinyGo") }
|
||||
|
||||
// Caller returns the hex pubkey of the transaction sender.
|
||||
func Caller() string { panic("dchain: Caller requires TinyGo") }
|
||||
|
||||
// BlockHeight returns the current block height.
|
||||
func BlockHeight() uint64 { panic("dchain: BlockHeight requires TinyGo") }
|
||||
|
||||
// Treasury returns the contract's ownerless treasury address.
|
||||
func Treasury() string { panic("dchain: Treasury requires TinyGo") }
|
||||
|
||||
// Balance returns the token balance of a hex pubkey in µT.
|
||||
func Balance(pubKey string) uint64 { panic("dchain: Balance requires TinyGo") }
|
||||
|
||||
// Transfer sends amount µT from one address to another.
|
||||
func Transfer(from, to string, amount uint64) bool { panic("dchain: Transfer requires TinyGo") }
|
||||
|
||||
// CallContract executes a method on another deployed contract.
|
||||
func CallContract(contractID, method, argsJSON string) bool {
|
||||
panic("dchain: CallContract requires TinyGo")
|
||||
}
|
||||
|
||||
// Log writes a message to the contract log.
|
||||
func Log(msg string) { panic("dchain: Log requires TinyGo") }
|
||||
Reference in New Issue
Block a user