12 KiB
FIPS Network View — sovereign://fips/network
Goal
Extend the FIPS management page with a "Network" view that shows the entire mesh as the local node knows it — not just direct peers, but every node the daemon has discovered via bloom-filter propagation and Nostr discovery. This answers "what can you see on FIPS?" and "can you get a view of the entire network?"
What the FIPS daemon exposes
The FIPS control protocol (verified in ~/lt/fips/src/control/) has
these read-only commands relevant to a network view:
| Command | What it returns | Network-view use |
|---|---|---|
show_status |
estimated_mesh_size (bloom-union cardinality), peer_count, tree_depth, is_root, root |
Header summary: "Mesh: ~N nodes, depth D" |
show_peers |
Direct authenticated peers with npub, display_name, ipv6_addr, connectivity, is_parent, is_child, tree_depth, transport_addr, transport_type, MMP metrics |
The "direct peers" table (already shown) |
show_tree |
Spanning-tree state: root, root_npub, my_node_addr, depth, parent, parent_display_name, peers[] (each with node_addr, display_name, depth, coords path, distance_to_us) |
Tree topology view — local node + 1-hop neighbors with their tree positions |
show_identity_cache |
All known nodes in the mesh: entries[] with node_addr, npub, display_name, ipv6_addr, last_seen_ms, age_ms, plus count and max_entries |
The mesh node list — every node the local daemon has learned about, not just direct peers |
show_bloom |
Per-peer bloom-filter state (filter sequence, estimated counts) | Advanced/debug — shows how node-discovery knowledge propagates |
show_routing |
Coord-cache entries, pending lookups, retries, forwarding counters | Advanced/debug — routing health |
Key insight: "the entire network" = identity cache + tree
The FIPS daemon does not have a "ask peer X what peers it has" command. The mesh is observed through two mechanisms:
-
Bloom filters — each node broadcasts a bloom filter of node addresses it knows about. The local node unions these to estimate total mesh size (
estimated_mesh_size) and caches individual node identities it has resolved (show_identity_cache). -
Spanning tree — each node declares a parent; the tree coordinates encode paths to the root.
show_treegives the local node's tree position and its neighbors' positions.
So "the entire network" as seen from this node is:
show_identity_cache→ the set of known nodes (npub, ipv6, name, last-seen). This is the node list for the network view.show_tree→ the local tree topology (who is my parent, who are my children, what is the root, what is the depth).show_status.estimated_mesh_size→ a single estimated total.
We cannot build a full graph of who-connects-to-whom from the local node's control socket alone — that would require every node to expose its peer list, and FIPS doesn't do that (bloom filters propagate existence, not adjacency). What we can show is:
- A node list (identity cache) — "these are all the nodes in the mesh that this node knows about."
- A tree view — "this is my position in the spanning tree, my parent, my children, the root."
- A mesh summary — "estimated N nodes total, tree depth D."
Design
Page layout
Add a "Network" tab/section to sovereign://fips (or a separate
sovereign://fips/network page linked from the main FIPS page).
┌─────────────────────────────────────────────────────────────┐
│ FIPS Mesh Network [Refresh] │
│ [Status] [Peers] [Network] [Tree] │
├─────────────────────────────────────────────────────────────┤
│ ┌─ Mesh Summary ────────────────────────────────────────┐ │
│ │ Estimated mesh size: 47 nodes │ │
│ │ Known nodes (cache): 23 │ │
│ │ Tree depth: 4 │ │
│ │ Root: npub1abc... (laantungir) │ │
│ │ Our position: depth 2, parent npub1def... │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌─ Known Nodes (23) ────────────────────────────────────┐ │
│ │ npub1abc... laantungir fd00::1 2s ago ● │ │
│ │ npub1def... fips.v0l.io fd00::2 5s ago ● │ │
│ │ npub1ghi... — fd00::3 1m ago ○ │ │
│ │ ... │ │
│ │ (● = direct peer, ○ = known via discovery only) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌─ Spanning Tree ───────────────────────────────────────┐ │
│ │ Root: npub1abc... (laantungir) │ │
│ │ Our parent: npub1def... (fips.v0l.io) │ │
│ │ Our children: │ │
│ │ npub1jkl... depth 3 distance 1 │ │
│ │ npub1mno... depth 3 distance 1 │ │
│ │ Tree peers (1-hop): │ │
│ │ npub1abc... depth 0 root distance 2 │ │
│ │ npub1def... depth 1 parent distance 1 │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
New JSON endpoints
| URI | FIPS command | Returns |
|---|---|---|
sovereign://fips/network |
show_status + show_identity_cache + show_tree |
Combined JSON for the network view |
sovereign://fips/tree |
show_tree |
Spanning tree JSON (for a dedicated tree tab) |
sovereign://fips/network
Calls three FIPS commands on a fresh control connection and combines the results:
{
"summary": {
"estimated_mesh_size": 47,
"known_nodes": 23,
"max_cache_entries": 256,
"tree_depth": 4,
"is_root": false,
"root_npub": "npub1abc...",
"root_display_name": "laantungir",
"our_node_addr": "e3da8293e6ea58807bd02b4749824243",
"our_parent": "npub1def...",
"our_parent_display_name": "fips.v0l.io"
},
"nodes": [
{
"node_addr": "e3da8293...",
"npub": "npub1abc...",
"display_name": "laantungir",
"ipv6_addr": "fde3:da82:...",
"last_seen_ms": 1784309108403,
"age_ms": 2000,
"is_direct_peer": true
}
],
"tree": {
"root": "1b4788b7...",
"root_npub": "npub1abc...",
"is_root": false,
"depth": 2,
"parent": "9d1192e8...",
"parent_display_name": "fips.v0l.io",
"peers": [
{
"node_addr": "1b4788b7...",
"display_name": "laantungir",
"depth": 0,
"distance_to_us": 2
}
]
}
}
The is_direct_peer flag on each node is computed by cross-referencing
the identity cache entries against the show_peers npub list (so the
UI can mark direct peers vs. discovery-only nodes).
Implementation
1. Add fips_control_show_tree() and fips_control_show_identity_cache() to src/fips_control.c / .h
These follow the existing fips_control_show_peers() pattern — send
the command, return the data field as a JSON string (caller frees):
char *fips_control_show_tree(int fd, char *error, size_t error_size);
char *fips_control_show_identity_cache(int fd, char *error, size_t error_size);
2. Add handle_fips_network_json() to src/nostr_bridge.c
Opens a control connection, calls show_status, show_peers,
show_identity_cache, and show_tree, then combines them into the
JSON above. The is_direct_peer flag is computed by building a set of
direct-peer npubs from show_peers and checking each identity-cache
entry against it.
3. Add routing for sovereign://fips/network and sovereign://fips/tree
if (strcmp(uri, "sovereign://fips/network") == 0 ||
strncmp(uri, "sovereign://fips/network?", 26) == 0) {
handle_fips_network_json(request);
return;
}
if (strcmp(uri, "sovereign://fips/tree") == 0 ||
strncmp(uri, "sovereign://fips/tree?", 22) == 0) {
handle_fips_tree_json(request);
return;
}
4. Add a "Network" tab to www/fips.html / www/fips.js
Add tab navigation (Status | Peers | Network | Tree) to the page. The
Network tab fetches sovereign://fips/network and renders:
- Mesh summary card (estimated size, known nodes, depth, root, our position).
- Known nodes table (npub, name, ipv6, last-seen, direct-peer badge).
- Spanning tree card (root, parent, children, 1-hop peers with distances).
The Tree tab fetches sovereign://fips/tree and renders a more
detailed tree view (coords paths, declaration sequence).
5. Auto-refresh
The Network tab polls sovereign://fips/network every 5s (same as the
status poll).
Limitations (what we cannot show)
- Full adjacency graph — FIPS doesn't expose "peer X's peer list." Bloom filters propagate node existence, not edges. To build a full graph, every node would need to expose its peer list (a future FIPS protocol extension), or the browser would need to query each node's control socket individually (not practical across the mesh).
- Real-time node join/leave — the identity cache has
last_seen_msandage_ms, so we can show staleness, but there's no event stream for join/leave (would need a FIPS control subscription protocol).
Files
src/
├── fips_control.c # Add show_tree(), show_identity_cache()
├── fips_control.h # Add declarations
├── nostr_bridge.c # Add handle_fips_network_json(), handle_fips_tree_json(), routing
www/
├── fips.html # Add Network + Tree tabs
├── fips.css # Tab + network-table styles
├── fips.js # Network + tree rendering, tab switching
Implementation phases
Phase 1: Network summary + known nodes
- Add
fips_control_show_tree()andfips_control_show_identity_cache() - Add
handle_fips_network_json()combining status + peers + identity cache - Add
sovereign://fips/networkroute - Add "Network" tab to the page with mesh summary + known-nodes table
- Mark direct peers with a badge
Phase 2: Spanning tree view
- Add
handle_fips_tree_json()(pass-through ofshow_tree) - Add
sovereign://fips/treeroute - Add "Tree" tab with root, parent, children, 1-hop peers, coords paths
- Optional: simple ASCII/indented tree rendering
Phase 3: Polish
- Sort known nodes by last-seen (most recent first)
- Color-code staleness (green < 10s, yellow < 1m, gray > 5m)
- Click a node to see its detail (ipv6, age, whether direct peer)
- Copy-to-clipboard for npub / ipv6
- Optional: bloom-filter visualization (advanced/debug tab)