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
94 lines
3.6 KiB
Go
94 lines
3.6 KiB
Go
// Package node — /api/well-known-version endpoint.
|
|
//
|
|
// Clients hit this to feature-detect the node they're talking to, without
|
|
// hardcoding "node >= 0.5 supports channels" into every screen. The response
|
|
// lists three coordinates a client cares about:
|
|
//
|
|
// - node_version — human-readable build tag (ldflags-injectable)
|
|
// - protocol_version — integer bumped only on wire-protocol breaking changes
|
|
// - features — stable string tags for "this binary implements X"
|
|
//
|
|
// Feature tags are ADDITIVE — once a tag ships in a release, it keeps being
|
|
// returned forever (even if the implementation moves around internally). The
|
|
// client uses them as "is this feature here or not?", not "what version is
|
|
// this feature at?". Versioning a feature is done by shipping a new tag
|
|
// (e.g. "channels_v2" alongside "channels_v1" for a deprecation window).
|
|
//
|
|
// Response shape:
|
|
//
|
|
// {
|
|
// "node_version": "0.5.0-dev",
|
|
// "protocol_version": 1,
|
|
// "features": [
|
|
// "channels_v1",
|
|
// "fan_out",
|
|
// "native_username_registry",
|
|
// "ws_submit_tx",
|
|
// "access_token"
|
|
// ],
|
|
// "chain_id": "dchain-ddb9a7e37fc8"
|
|
// }
|
|
package node
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"sort"
|
|
|
|
"go-blockchain/node/version"
|
|
)
|
|
|
|
// ProtocolVersion is bumped only when the wire-protocol changes in a way
|
|
// that a client compiled against version N cannot talk to a node at
|
|
// version N+1 (or vice versa) without updating. Adding new optional fields,
|
|
// new EventTypes, new WS ops, new HTTP endpoints — none of those bump this.
|
|
//
|
|
// Bumping this means a coordinated release: every client and every node
|
|
// operator must update before the old version stops working.
|
|
const ProtocolVersion = 1
|
|
|
|
// nodeFeatures is the baked-in list of feature tags this binary implements.
|
|
// Append-only. When you add a new tag, add it here AND document what it means
|
|
// so clients can feature-detect reliably.
|
|
//
|
|
// Naming convention: snake_case, versioned suffix for anything that might get
|
|
// a breaking successor (e.g. `channels_v1`, not `channels`).
|
|
var nodeFeatures = []string{
|
|
"access_token", // DCHAIN_API_TOKEN gating on writes (+ optional reads)
|
|
"channels_v1", // /api/channels/:id + /members with X25519 enrichment
|
|
"chain_id", // /api/network-info returns chain_id
|
|
"contract_logs", // /api/contract/:id/logs endpoint
|
|
"fan_out", // client-side per-recipient envelope sealing
|
|
"identity_registry", // /api/identity/:pub returns X25519 pub + relay hints
|
|
"native_username_registry", // native:username_registry contract
|
|
"onboarding_api", // /api/network-info for joiner bootstrap
|
|
"payment_channels", // off-chain payment channel open/close
|
|
"relay_mailbox", // /relay/send + /relay/inbox
|
|
"ws_submit_tx", // WebSocket submit_tx op
|
|
}
|
|
|
|
func registerWellKnownVersionAPI(mux *http.ServeMux, q ExplorerQuery) {
|
|
mux.HandleFunc("/api/well-known-version", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
jsonErr(w, fmt.Errorf("method not allowed"), 405)
|
|
return
|
|
}
|
|
// Return a copy so callers can't mutate the shared slice.
|
|
feats := make([]string, len(nodeFeatures))
|
|
copy(feats, nodeFeatures)
|
|
sort.Strings(feats)
|
|
|
|
resp := map[string]any{
|
|
"node_version": version.Tag,
|
|
"build": version.Info(),
|
|
"protocol_version": ProtocolVersion,
|
|
"features": feats,
|
|
}
|
|
// Include chain_id if the node exposes it (same helper as network-info).
|
|
if q.ChainID != nil {
|
|
resp["chain_id"] = q.ChainID()
|
|
}
|
|
jsonOK(w, resp)
|
|
})
|
|
}
|