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:
vsecoder
2026-04-17 14:16:44 +03:00
commit 7e7393e4f8
196 changed files with 55947 additions and 0 deletions

116
node/api_well_known.go Normal file
View File

@@ -0,0 +1,116 @@
// Package node — /api/well-known-contracts endpoint.
//
// This endpoint lets a freshly-launched client auto-discover the canonical
// contract IDs for system services (username registry, governance, …) without
// the user having to paste contract IDs into settings by hand.
//
// Discovery strategy:
//
// 1. List all deployed contracts via q.GetContracts().
// 2. For each contract, parse its ABI JSON and pull the "contract" name field.
// 3. For each distinct name, keep the **earliest-deployed** record — this is
// the canonical one. Operators who want to override this (e.g. migrate to
// a new registry) will add pinning support via config later; for the MVP
// "earliest wins" matches what all nodes see because the chain is ordered.
//
// The response shape is stable JSON so the client can rely on it:
//
// {
// "count": 3,
// "contracts": {
// "username_registry": { "contract_id": "…", "name": "username_registry", "version": "1.0.0", "deployed_at": 42 },
// "governance": { "contract_id": "…", "name": "governance", "version": "0.9.0", "deployed_at": 50 },
// …
// }
// }
package node
import (
"encoding/json"
"fmt"
"net/http"
)
// WellKnownContract is the per-entry payload returned in /api/well-known-contracts.
type WellKnownContract struct {
ContractID string `json:"contract_id"`
Name string `json:"name"`
Version string `json:"version,omitempty"`
DeployedAt uint64 `json:"deployed_at"`
}
// abiHeader is the minimal subset of a contract's ABI JSON we need to look at.
type abiHeader struct {
Contract string `json:"contract"`
Version string `json:"version"`
}
func registerWellKnownAPI(mux *http.ServeMux, q ExplorerQuery) {
mux.HandleFunc("/api/well-known-contracts", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
jsonErr(w, fmt.Errorf("method not allowed"), 405)
return
}
if q.GetContracts == nil {
jsonErr(w, fmt.Errorf("contract queries not available on this node"), 503)
return
}
all, err := q.GetContracts()
if err != nil {
jsonErr(w, err, 500)
return
}
out := map[string]WellKnownContract{}
// WASM contracts (stored as ContractRecord in BadgerDB).
for _, rec := range all {
if rec.ABIJson == "" {
continue
}
var abi abiHeader
if err := json.Unmarshal([]byte(rec.ABIJson), &abi); err != nil {
continue
}
if abi.Contract == "" {
continue
}
existing, ok := out[abi.Contract]
if !ok || rec.DeployedAt < existing.DeployedAt {
out[abi.Contract] = WellKnownContract{
ContractID: rec.ContractID,
Name: abi.Contract,
Version: abi.Version,
DeployedAt: rec.DeployedAt,
}
}
}
// Native (in-process Go) contracts. These always win over WASM
// equivalents of the same ABI name — the native implementation is
// authoritative because every node runs identical Go code, while a
// WASM copy might drift (different build, different bytecode).
if q.NativeContracts != nil {
for _, nc := range q.NativeContracts() {
var abi abiHeader
if err := json.Unmarshal([]byte(nc.ABIJson), &abi); err != nil {
continue
}
if abi.Contract == "" {
continue
}
out[abi.Contract] = WellKnownContract{
ContractID: nc.ContractID,
Name: abi.Contract,
Version: abi.Version,
DeployedAt: 0, // native contracts exist from block 0
}
}
}
jsonOK(w, map[string]any{
"count": len(out),
"contracts": out,
})
})
}