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:
116
node/api_well_known.go
Normal file
116
node/api_well_known.go
Normal 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,
|
||||
})
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user