// 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) }) }