规模化联邦方案(去火管化:每群/频道独立主题 + DHT/见证发现 + 真 nm-federation + NAT 直投 + E2E 配对)
现有联邦靠一条 gossip 主题全量广播所有群/频道/私聊/命名,
nm-federation仍是 16 行桩。
目标:按主题订阅——流量与可见性收敛到相关方,可扩展、无单点、为 E2E 预留。
这是一条纯后端 crate 线(crates/nm-federation/nm-gossip/nm-node),惠及桌面+移动,不碰clients/app与clients/mobile,可与移动端并行。
基线见CURRENT_STATE_ANALYSIS.md。本文为方案稿(评审后动手)。
0. 背景与「火管」三宗罪
当前跨节点收敛(群/频道/私聊/命名)统一走一个联邦级 gossip 主题nmspace-groups:<fed>,承载GroupGossip{ Group announce | Gram msg | Gram direct | NameRecord },全量广播给联邦内每个节点,各记录按origin去自环、LWW/墓碑合并。它当初是为绕开「跨 NAT s2s 直拨超时」而快速打通的权宜之计,但:
- 隐私:非成员节点也收到所有群/频道/私聊的元数据与明文(E2E 未接)。
- 规模:带宽/存储 ∝
O(节点数 × 全量消息)——联邦越大越不可持续。 - 架构债:
nm-federation是桩,真正的收敛逻辑散在nm-node的 gossip hack 里,难演进。
1. 设计目标与原则
- 最小可见性:一条消息只到达它该到的成员/订阅者。
- 可扩展:单节点开销随其实际所在的群/频道数,而非联邦规模。
- 无单点、可自托管:发现可去中心(DHT),弱锚(见证节点)仅做 bootstrap。
- 兼容既有:复用 dial-by-key、
GroupGossip信封、LWW/墓碑、content_hash去重。 - 平滑迁移:灰度双写、版本协商,不停机切换。
- 为 E2E 预留:主题边界 ↔ 加密群边界(MLS)天然对齐。
2. 架构总览
3. 主题模型(Topic Model)
| 对象 | 主题 | 谁 join | 收敛 |
|---|---|---|---|
| 群 | nmspace-group:<gid> | 群成员 | 消息=gossip;成员/元信息=iroh-docs |
| 频道 | nmspace-channel:<cid> | 订阅者 | 同上(频道现为内存态,顺带持久化) |
| 命名 | nmspace-names:<domain> | 关心该域的节点 | NameRecord LWW/墓碑(见 §7) |
| 私聊 | 不上广播主题 | —— | 直投 / 收件箱主题(见 §6) |
GroupGossip信封基本复用,但按 topic 隔离;origin去自环、content_hash去重照旧。- 一个节点可同时在很多主题里;由
nm-federation统一管理其订阅集与生命周期。
4. 发现与引导(Discovery & Bootstrap)
- 症结:
iroh-gossipjoin 一个主题需要已知 bootstrap peer;而非成员根本不在主题里,无从得知第一个 peer。 - 方案 A(DHT / pkarr):把
群公钥 → {topic, 种子 peer 列表, 成员根哈希}发布到 iroh/pkarr 的 DHT,键=群公钥;新成员查询即得 bootstrap。去中心、无锚。 - 方案 B(见证/锚定节点 witness/anchor):每群指定少量锚定节点(创建者 home + 若干,可轮换),常驻该主题、对外 dial-by-key 可达,充当稳定 bootstrap + 成员目录。弱锚、可多副本。
- 推荐 A+B 混合:锚定节点给稳定冷启动,DHT 做无锚兜底;群公钥不可枚举,避免群被遍历发现。
5. nm-federation 真实化(从桩到模块)
把散落在nm-node的 gossip 收敛逻辑收敛进crates/nm-federation,职责分层:
- 传输/实时:
iroh-gossip每主题一个GossipTopic——低延迟消息广播。 - 状态/收敛:
iroh-docs每群一个复制文档(成员集、角色、群资料、墓碑),只在成员间同步——强收敛、可回放。 - 订阅生命周期:join/leave/rejoin、announce 心跳、peer 采样与重连、主题 GC。
API 草图:
pubtraitFederation{asyncfnjoin_group(&self,gid:GroupId)->Result<GroupHandle>;asyncfnleave_group(&self,gid:GroupId)->Result<()>;asyncfnpublish(&self,gid:GroupId,env:GroupGossip)->Result<()>;fnsubscribe(&self,gid:GroupId)->implStream<Item=GroupGossip>;asyncfnmembers(&self,gid:GroupId)->Result<MemberSet>;// 来自 iroh-docs}注意版本对齐:
iroh 1.2与iroh-gossip/iroh-docs 0.10x的兼容是前置工作(分析里已标记)。
6. 私聊与 NAT 直投症结
- 根因:私聊被塞进火管,是因为跨 NAT 的 s2s 直拨当初超时(
fed sync failed: s2s request timed out)。去火管的前提是恢复 NAT 健壮的直投。 - 路径2(首选):修好 irohrelay + holepunch + 连接复用;收件人在线→直投,离线→入其home node 的 redb inbox补投(home 经 relay 稳定可达)。第一要务是定位当初超时真因:relay url 未配 / ALPN 不符 / 每次新建连接未复用 / dial 目标是节点而非其 home。
- 路径3(兜底):
nmspace-inbox:<recipient_pub>每收件人一主题,仅收件人与其 home join;发件方 publish。避免全广播又不依赖直拨;元数据仅对 join 者可见,配 E2E 缓解。
7. 命名去火管化
- 现:
NameRecord走火管。目标:按域主题nmspace-names:<domain>,只有关心该域的节点 join;或并入 DHT(domain→node本就经 nm-domain/pkarr 发现)。 - LWW + 墓碑(空
client_pubkey+ 更高 serial)语义不变;home_node自签名不变。
8. E2E(MLS)配对
- 每群一主题 ↔ 每群一个 MLS 群:预留的 openmls 终于可接。主题传输密文 + MLS 握手(Welcome/Commit);成员增减 ↔ MLS add/remove(与 §5 的成员文档联动)。
- 私聊用 1:1 MLS 或 X25519 封装。去火管 + E2E 一起,才真正消除「明文广播全联邦」。
9. 迁移与兼容(灰度,不停机)
- 双写:节点同时在火管 + 新 per-topic 发;收端按
content_hash去重。 - 协商:
announce带supports_per_topicfeature flag,混合网络平滑过渡。 - 切流:成员/订阅迁到新主题后,发端停火管、仅新主题。
- 退役:联邦主题仅作「发现兜底 / 老节点兼容」保留一段时间后移除。
10. 分阶段路线(每阶段可跨网验证)
- F0 · nm-federation 骨架:topic 注册表 + 订阅 API + 单测;内部仍走火管,线上行为不变。
- F1 · 群/频道切 per-topic:成员 join 自己群主题;双写 + 去重;锚定节点 bootstrap。
- F2 · DHT/pkarr 发现:无锚也能 join;群公钥不可枚举。
- F3 · 私聊去火管:定位并修 NAT 直投;inbox 主题兜底。
- F4 · 命名去火管:按域主题 / DHT。
- F5 · E2E + 退役火管:每群 MLS 接入;联邦火管下线。
- 里程碑沿用现有多 seed + 集成测试 + 跨网真机(见 memory
seed-deploy-pipeline/federation-delivery-mechanisms)。
11. 风险与开放问题
- 多主题开销:节点在大量群时,N 个 gossip 主题的连接/采样/内存——需 peer 采样上限与懒 join。
- iroh-docs × iroh 1.2 版本对齐(前置)。
- NAT 直投真因未定位:F3 前必须用真机 + relay 实测复现。
- 见证节点信任/激励:谁当锚、下线与轮换、防女巫。
- DHT 隐私/可用性:发现延迟、群枚举(靠不可枚举群公钥缓解)。
- 墓碑/LWW GC:per-topic 下的垃圾回收与存储增长。
- 开放问题:
- 频道是否也要成员鉴权(公开频道 vs 私有频道的主题可见性)?
- 私聊兜底选「直投为主」还是「inbox 主题为主」,取决于 relay 实测结论。
- E2E 先上私聊还是先上群?(群的成员变更+MLS 更复杂)
- 是否顺带把频道做持久化(现为内存态,重启丢)。