@steve02081504/fount-p2p 0.0.26 → 0.0.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/core/bytes_codec.mjs +5 -6
- package/core/composite_key.mjs +0 -79
- package/core/entity_id_parse.mjs +2 -3
- package/core/evfs_logical_path.mjs +2 -3
- package/core/hexIds.mjs +5 -5
- package/core/logical_entity.mjs +3 -4
- package/crypto/channel.mjs +2 -25
- package/crypto/checkpoint_sign.mjs +5 -4
- package/crypto/crypto.mjs +0 -8
- package/crypto/key.mjs +9 -10
- package/dag/canonicalize_row.mjs +1 -1
- package/discovery/advert_peer_hints.mjs +1 -1
- package/discovery/adverts.mjs +7 -12
- package/discovery/bt/index.mjs +6 -11
- package/discovery/bt/peer_hints.mjs +2 -3
- package/discovery/bt/runtime.mjs +1 -1
- package/discovery/index.mjs +14 -14
- package/discovery/lan.mjs +3 -3
- package/discovery/lan_interfaces.mjs +2 -2
- package/discovery/lan_peer_hints.mjs +1 -1
- package/discovery/nostr.mjs +19 -24
- package/federation/entity_key_chain.mjs +5 -24
- package/federation/message_rate_limit.mjs +3 -2
- package/federation/part_query/runtime.mjs +9 -20
- package/files/assemble.mjs +3 -3
- package/files/assemble_stream.mjs +1 -1
- package/files/chunk/fetch.mjs +1 -2
- package/files/chunk/responder.mjs +4 -41
- package/files/chunk/store.mjs +2 -3
- package/files/chunk/verify.mjs +2 -3
- package/files/evfs.mjs +1 -1
- package/files/evfs_ref.mjs +3 -4
- package/files/fetch_fanout.mjs +3 -3
- package/files/manifest/fetch.mjs +2 -3
- package/files/manifest/normalize.mjs +9 -9
- package/files/manifest/pending.mjs +1 -4
- package/files/manifest/public.mjs +8 -8
- package/files/transfer_key.mjs +3 -3
- package/files/transfer_key_registry.mjs +6 -6
- package/governance/branch.mjs +2 -1
- package/governance/join_pow.mjs +7 -5
- package/governance/owner_succession_ballot.mjs +6 -6
- package/index.mjs +8 -2
- package/infra/default_node_dir.mjs +2 -2
- package/infra/priority.mjs +2 -2
- package/link/frame.mjs +2 -3
- package/link/handshake.mjs +6 -6
- package/link/pipe.mjs +69 -5
- package/link/providers/ble_gatt.mjs +1 -9
- package/link/providers/index.mjs +2 -2
- package/link/providers/nostr.mjs +1 -3
- package/link/providers/webrtc.mjs +2 -2
- package/link/rtc/ice_local_hostname.mjs +6 -5
- package/link/rtc/w3c_bridge.mjs +1 -1
- package/link/sdp_fingerprint.mjs +1 -1
- package/mailbox/consumer_registry.mjs +2 -2
- package/mailbox/deliver_or_store.mjs +4 -4
- package/mailbox/parse.mjs +1 -1
- package/mailbox/prune.mjs +2 -2
- package/mailbox/rate.mjs +3 -10
- package/mailbox/store.mjs +7 -7
- package/node/denylist.mjs +38 -38
- package/node/entity_store.mjs +5 -5
- package/node/identity.mjs +7 -8
- package/node/instance.mjs +1 -1
- package/node/log.mjs +1 -1
- package/node/network.mjs +14 -16
- package/node/personal_block.mjs +22 -20
- package/node/reputation_store.mjs +11 -19
- package/node/reputation_sync.mjs +0 -7
- package/node/signaling_config.mjs +1 -1
- package/node/storage_plugins.mjs +2 -2
- package/overlay/index.mjs +1 -1
- package/package.json +1 -1
- package/registries/event_type.mjs +2 -2
- package/registries/room_provider.mjs +2 -2
- package/reputation/engine.mjs +17 -15
- package/schemas/discovery.mjs +5 -5
- package/schemas/federation_pull.mjs +16 -24
- package/schemas/part_query.mjs +4 -4
- package/timeline/verify_remote.mjs +4 -4
- package/transport/group_link_set.mjs +3 -3
- package/transport/ice_servers.mjs +2 -2
- package/transport/link_registry.mjs +51 -20
- package/transport/mesh_keepalive.mjs +1 -1
- package/transport/node_scope/wire.mjs +1 -1
- package/transport/peer_health.mjs +127 -0
- package/transport/remote_user_room.mjs +13 -15
- package/transport/rtc_connection_budget.mjs +6 -6
- package/transport/runtime_bootstrap.mjs +12 -12
- package/trust_graph/cache.mjs +2 -3
- package/trust_graph/engine.mjs +1 -1
- package/trust_graph/registry.mjs +2 -2
- package/trust_graph/send.mjs +1 -1
- package/utils/async_mutex.mjs +1 -1
- package/wire/ingress.mjs +1 -1
- package/wire/part/ingress.mjs +6 -6
- package/wire/part/query.mjs +3 -3
- package/wire/volatile_signature.mjs +4 -9
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { normalizeHex64 } from '../core/hexIds.mjs'
|
|
2
|
+
import { emitSafe } from '../utils/emit_safe.mjs'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {{
|
|
6
|
+
* nodeHash: string
|
|
7
|
+
* connected: boolean
|
|
8
|
+
* rttMs: number | null
|
|
9
|
+
* avgRttMs: number | null
|
|
10
|
+
* lastSeenAt: number
|
|
11
|
+
* source: string | null
|
|
12
|
+
* }} PeerHealthEntry
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 聚合邻居链路健康:在 mesh/link 常态维护期间顺带统计各邻居的 RTT 与可达性。
|
|
17
|
+
* 订阅 registry 的 link up/down,并对每条活跃链路监听 onRtt/onDown。
|
|
18
|
+
* @param {object} registry link registry(onLinkUp / onLinkDown)
|
|
19
|
+
* @returns {{
|
|
20
|
+
* getPeerHealth: (nodeHash: string) => PeerHealthEntry | null
|
|
21
|
+
* listPeerHealth: () => PeerHealthEntry[]
|
|
22
|
+
* onPeerHealth: (listener: (nodeHash: string, entry: PeerHealthEntry) => void) => () => void
|
|
23
|
+
* stop: () => void
|
|
24
|
+
* }} peer health 聚合器
|
|
25
|
+
*/
|
|
26
|
+
export function createPeerHealthTracker(registry) {
|
|
27
|
+
/** @type {Map<string, PeerHealthEntry>} */
|
|
28
|
+
const entries = new Map()
|
|
29
|
+
/** @type {Map<string, () => void>} 每条链路在 registry/link 上的取消订阅回调 */
|
|
30
|
+
const cleanups = new Map()
|
|
31
|
+
/** @type {Set<(nodeHash: string, entry: PeerHealthEntry) => void>} */
|
|
32
|
+
const listeners = new Set()
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 合并补丁并通知订阅方。
|
|
36
|
+
* @param {string} nodeHash 远端节点 64 hex
|
|
37
|
+
* @param {Partial<PeerHealthEntry>} patch 补丁
|
|
38
|
+
* @returns {void}
|
|
39
|
+
*/
|
|
40
|
+
function update(nodeHash, patch) {
|
|
41
|
+
const entry = entries.get(nodeHash) ?? {
|
|
42
|
+
nodeHash,
|
|
43
|
+
connected: false,
|
|
44
|
+
rttMs: null,
|
|
45
|
+
avgRttMs: null,
|
|
46
|
+
lastSeenAt: 0,
|
|
47
|
+
source: null,
|
|
48
|
+
}
|
|
49
|
+
if (patch.connected !== undefined) entry.connected = patch.connected
|
|
50
|
+
if (patch.rttMs !== undefined) entry.rttMs = patch.rttMs
|
|
51
|
+
if (patch.avgRttMs !== undefined) entry.avgRttMs = patch.avgRttMs
|
|
52
|
+
if (patch.lastSeenAt !== undefined) entry.lastSeenAt = patch.lastSeenAt
|
|
53
|
+
if (patch.source !== undefined) entry.source = patch.source
|
|
54
|
+
entries.set(nodeHash, entry)
|
|
55
|
+
emitSafe(listeners, nodeHash, entry)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const stopUp = registry.onLinkUp?.((nodeHash, link) => {
|
|
59
|
+
const hash = normalizeHex64(nodeHash)
|
|
60
|
+
if (!hash) return
|
|
61
|
+
const stopRtt = link?.onRtt?.(() => {
|
|
62
|
+
const stats = link.stats?.() ?? {}
|
|
63
|
+
update(hash, {
|
|
64
|
+
rttMs: stats.rttMs ?? null,
|
|
65
|
+
avgRttMs: stats.avgRttMs ?? null,
|
|
66
|
+
lastSeenAt: Date.now(),
|
|
67
|
+
})
|
|
68
|
+
}) ?? null
|
|
69
|
+
const stopDown = link?.onDown?.(() => {
|
|
70
|
+
cleanups.get(hash)?.()
|
|
71
|
+
cleanups.delete(hash)
|
|
72
|
+
update(hash, { connected: false, lastSeenAt: Date.now() })
|
|
73
|
+
}) ?? null
|
|
74
|
+
update(hash, {
|
|
75
|
+
connected: true,
|
|
76
|
+
source: link?.providerId ?? null,
|
|
77
|
+
lastSeenAt: Date.now(),
|
|
78
|
+
})
|
|
79
|
+
cleanups.set(hash, () => {
|
|
80
|
+
stopRtt?.()
|
|
81
|
+
stopDown?.()
|
|
82
|
+
})
|
|
83
|
+
}) ?? null
|
|
84
|
+
const stopDown = registry.onLinkDown?.(nodeHash => {
|
|
85
|
+
const hash = normalizeHex64(nodeHash)
|
|
86
|
+
if (!hash) return
|
|
87
|
+
cleanups.get(hash)?.()
|
|
88
|
+
cleanups.delete(hash)
|
|
89
|
+
update(hash, { connected: false, lastSeenAt: Date.now() })
|
|
90
|
+
}) ?? null
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
/**
|
|
94
|
+
* @param {string} nodeHash 远端节点 64 hex
|
|
95
|
+
* @returns {PeerHealthEntry | null} 健康记录;无记录时 null
|
|
96
|
+
*/
|
|
97
|
+
getPeerHealth(nodeHash) {
|
|
98
|
+
return entries.get(normalizeHex64(nodeHash)) ?? null
|
|
99
|
+
},
|
|
100
|
+
/**
|
|
101
|
+
* @returns {PeerHealthEntry[]} 所有邻居健康记录
|
|
102
|
+
*/
|
|
103
|
+
listPeerHealth() {
|
|
104
|
+
return [...entries.values()]
|
|
105
|
+
},
|
|
106
|
+
/**
|
|
107
|
+
* @param {(nodeHash: string, entry: PeerHealthEntry) => void} listener 变化回调
|
|
108
|
+
* @returns {() => void} 取消订阅
|
|
109
|
+
*/
|
|
110
|
+
onPeerHealth(listener) {
|
|
111
|
+
listeners.add(listener)
|
|
112
|
+
return () => listeners.delete(listener)
|
|
113
|
+
},
|
|
114
|
+
/**
|
|
115
|
+
* 停止监听并清空聚合。
|
|
116
|
+
* @returns {void}
|
|
117
|
+
*/
|
|
118
|
+
stop() {
|
|
119
|
+
stopUp?.()
|
|
120
|
+
stopDown?.()
|
|
121
|
+
for (const cleanup of cleanups.values()) cleanup()
|
|
122
|
+
cleanups.clear()
|
|
123
|
+
entries.clear()
|
|
124
|
+
listeners.clear()
|
|
125
|
+
},
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -23,21 +23,19 @@ registerFederationRoomProvider('remote-user-room', () => {
|
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* 加入目标节点的用户房间(幂等)。
|
|
26
|
-
* @param {string} username 本地 replica 用户名
|
|
27
26
|
* @param {string} targetNodeHash 目标节点 64 hex
|
|
28
27
|
* @returns {Promise<RemoteUserRoomSlot>} 房间槽
|
|
29
28
|
*/
|
|
30
|
-
export async function ensureRemoteUserRoom(
|
|
31
|
-
const
|
|
32
|
-
const existing = slots.get(key)
|
|
29
|
+
export async function ensureRemoteUserRoom(targetNodeHash) {
|
|
30
|
+
const existing = slots.get(targetNodeHash)
|
|
33
31
|
if (existing) return existing
|
|
34
|
-
const inflight = inflights.get(
|
|
32
|
+
const inflight = inflights.get(targetNodeHash)
|
|
35
33
|
if (inflight) return await inflight
|
|
36
34
|
|
|
37
35
|
const task = (async () => {
|
|
38
36
|
try {
|
|
39
|
-
if (!await ensureLinkToNode(
|
|
40
|
-
throw new Error(`p2p: ensureRemoteUserRoom link failed for ${
|
|
37
|
+
if (!await ensureLinkToNode(targetNodeHash))
|
|
38
|
+
throw new Error(`p2p: ensureRemoteUserRoom link failed for ${targetNodeHash}`)
|
|
41
39
|
|
|
42
40
|
/** @type {import('../registries/room_provider.mjs').FederationRoomSlot} */
|
|
43
41
|
const roomSlot = {
|
|
@@ -46,13 +44,13 @@ export async function ensureRemoteUserRoom(username, targetNodeHash) {
|
|
|
46
44
|
* 返回远端用户房间 roster(链路存在时含目标节点)。
|
|
47
45
|
* @returns {Array<{ peerId: string, remoteNodeHash: string }>} roster 列表
|
|
48
46
|
*/
|
|
49
|
-
getRoster: () => getLink(
|
|
47
|
+
getRoster: () => getLink(targetNodeHash) ? [{ peerId: targetNodeHash, remoteNodeHash: targetNodeHash }] : [],
|
|
50
48
|
/**
|
|
51
49
|
* 按 nodeHash 查找 peer id。
|
|
52
50
|
* @param {string} nh 目标节点 64 hex
|
|
53
51
|
* @returns {string | null} 对端 id;无链路时为 null
|
|
54
52
|
*/
|
|
55
|
-
getPeerIdByNodeHash: nh => getLink(nh) ?
|
|
53
|
+
getPeerIdByNodeHash: nh => getLink(nh) ? nh : null,
|
|
56
54
|
/**
|
|
57
55
|
* 经 node scope 向远端 peer 发送 action。始终走当前规范链路(`getLink`),
|
|
58
56
|
* 因为 glare 双 PC 择一后最初返回的链路可能已被关闭,规范链在 registry 内。
|
|
@@ -62,7 +60,7 @@ export async function ensureRemoteUserRoom(username, targetNodeHash) {
|
|
|
62
60
|
* @returns {void}
|
|
63
61
|
*/
|
|
64
62
|
sendToPeer(peerId, actionName, payload) {
|
|
65
|
-
void getLink(
|
|
63
|
+
void getLink(targetNodeHash)?.send({ scope: 'node', action: actionName, payload }).catch(() => { })
|
|
66
64
|
},
|
|
67
65
|
}
|
|
68
66
|
|
|
@@ -73,19 +71,19 @@ export async function ensureRemoteUserRoom(username, targetNodeHash) {
|
|
|
73
71
|
* @returns {Promise<void>} 关闭完成
|
|
74
72
|
*/
|
|
75
73
|
leave() {
|
|
76
|
-
slots.delete(
|
|
77
|
-
return closeLink(
|
|
74
|
+
slots.delete(targetNodeHash)
|
|
75
|
+
return closeLink(targetNodeHash, 'remote-user-room-release')
|
|
78
76
|
},
|
|
79
77
|
}
|
|
80
|
-
slots.set(
|
|
78
|
+
slots.set(targetNodeHash, slot)
|
|
81
79
|
bumpLocalDataRevision()
|
|
82
80
|
return slot
|
|
83
81
|
}
|
|
84
82
|
finally {
|
|
85
|
-
inflights.delete(
|
|
83
|
+
inflights.delete(targetNodeHash)
|
|
86
84
|
}
|
|
87
85
|
})()
|
|
88
86
|
|
|
89
|
-
inflights.set(
|
|
87
|
+
inflights.set(targetNodeHash, task)
|
|
90
88
|
return await task
|
|
91
89
|
}
|
|
@@ -48,7 +48,7 @@ function bucketFor(roomKey, limits = {}) {
|
|
|
48
48
|
budgets.set(roomKey, bucket)
|
|
49
49
|
}
|
|
50
50
|
for (const id of resolveRtcBudgetLimits(limits).trustedPeers || [])
|
|
51
|
-
bucket.trustedPeers.add(
|
|
51
|
+
bucket.trustedPeers.add(id)
|
|
52
52
|
return bucket
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -89,7 +89,7 @@ export function takeRtcJoinSlot(roomKey, peerId, limits = {}, sourceId = 'peer')
|
|
|
89
89
|
for (const id of bucket.active)
|
|
90
90
|
if (!bucket.trustedPeers.has(id)) nonTrustedCount++
|
|
91
91
|
if (!isTrusted) {
|
|
92
|
-
const source =
|
|
92
|
+
const source = sourceId || 'peer'
|
|
93
93
|
let sameSource = 0
|
|
94
94
|
for (const peerSource of bucket.sourceByPeer.values())
|
|
95
95
|
if (peerSource === source) sameSource++
|
|
@@ -108,7 +108,7 @@ export function takeRtcJoinSlot(roomKey, peerId, limits = {}, sourceId = 'peer')
|
|
|
108
108
|
bucket.joinTimestamps.push(now)
|
|
109
109
|
if (peerId) {
|
|
110
110
|
bucket.active.add(peerId)
|
|
111
|
-
bucket.sourceByPeer.set(peerId,
|
|
111
|
+
bucket.sourceByPeer.set(peerId, sourceId || 'peer')
|
|
112
112
|
}
|
|
113
113
|
return true
|
|
114
114
|
}
|
|
@@ -123,9 +123,9 @@ export function takeRtcJoinSlot(roomKey, peerId, limits = {}, sourceId = 'peer')
|
|
|
123
123
|
export function annotateRtcPeerNodeHash(roomKey, peerId, nodeHash, limits = {}) {
|
|
124
124
|
const bucket = budgets.get(roomKey)
|
|
125
125
|
if (!bucket || !peerId || !nodeHash) return
|
|
126
|
-
bucket.peerNodeHash.set(peerId,
|
|
126
|
+
bucket.peerNodeHash.set(peerId, nodeHash)
|
|
127
127
|
for (const trusted of resolveRtcBudgetLimits(limits).trustedPeers || [])
|
|
128
|
-
if (
|
|
128
|
+
if (trusted === nodeHash)
|
|
129
129
|
bucket.trustedPeers.add(peerId)
|
|
130
130
|
|
|
131
131
|
}
|
|
@@ -139,7 +139,7 @@ export function annotateRtcPeerNodeHash(roomKey, peerId, nodeHash, limits = {})
|
|
|
139
139
|
export function setRtcPeerSource(roomKey, peerId, sourceId) {
|
|
140
140
|
const bucket = budgets.get(roomKey)
|
|
141
141
|
if (!bucket || !peerId) return
|
|
142
|
-
bucket.sourceByPeer.set(peerId,
|
|
142
|
+
bucket.sourceByPeer.set(peerId, sourceId || 'peer')
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
/**
|
|
@@ -41,14 +41,14 @@ export function collectFastListenProviders(ownedLanTcp) {
|
|
|
41
41
|
const listenProviders = []
|
|
42
42
|
if (ownedLanTcp) listenProviders.push(ownedLanTcp)
|
|
43
43
|
for (const provider of listLinkProviders()) {
|
|
44
|
-
const id =
|
|
44
|
+
const { id } = provider
|
|
45
45
|
if (id.startsWith('lan_tcp') || id.startsWith('ble_gatt')) continue
|
|
46
|
-
if (
|
|
46
|
+
if (!provider.ensureListening) continue
|
|
47
47
|
if (providerHasNativeProbe(provider)) continue
|
|
48
|
-
if (
|
|
48
|
+
if (provider.isAvailable)
|
|
49
49
|
try {
|
|
50
50
|
const available = provider.isAvailable()
|
|
51
|
-
if (available
|
|
51
|
+
if (available?.then) continue
|
|
52
52
|
if (!available) continue
|
|
53
53
|
}
|
|
54
54
|
catch { continue }
|
|
@@ -166,7 +166,7 @@ export function createRuntimeBootstrap(deps) {
|
|
|
166
166
|
* @returns {number | null} 本机 lan_tcp 监听端口,未就绪为 null
|
|
167
167
|
*/
|
|
168
168
|
function lanTcpPort() {
|
|
169
|
-
const endpoint =
|
|
169
|
+
const endpoint = ownedLanTcp?.localEndpoint ? ownedLanTcp.localEndpoint() : null
|
|
170
170
|
return normalizeTcpPort(endpoint?.port)
|
|
171
171
|
}
|
|
172
172
|
|
|
@@ -199,13 +199,13 @@ export function createRuntimeBootstrap(deps) {
|
|
|
199
199
|
* @returns {Promise<void>}
|
|
200
200
|
*/
|
|
201
201
|
async function startProviderListening(provider) {
|
|
202
|
-
if (
|
|
202
|
+
if (!provider.ensureListening) return
|
|
203
203
|
try {
|
|
204
204
|
const stop = await provider.ensureListening({
|
|
205
205
|
localIdentity,
|
|
206
206
|
onInbound: onInboundLink,
|
|
207
207
|
})
|
|
208
|
-
if (
|
|
208
|
+
if (stop) stopLinkListeners.push(stop)
|
|
209
209
|
}
|
|
210
210
|
catch { /* provider listen unavailable */ }
|
|
211
211
|
}
|
|
@@ -228,18 +228,18 @@ export function createRuntimeBootstrap(deps) {
|
|
|
228
228
|
if (!providerIds.has('bt')) {
|
|
229
229
|
const bt = await import('../discovery/bt/index.mjs').catch(() => null)
|
|
230
230
|
if (generation !== gen || !isLive()) return
|
|
231
|
-
if (await bt?.
|
|
231
|
+
if (await bt?.canUseBluetoothRuntime?.()) {
|
|
232
232
|
if (generation !== gen || !isLive()) return
|
|
233
233
|
const provider = bt.createBluetoothDiscoveryProvider()
|
|
234
234
|
registerDiscoveryProvider(provider)
|
|
235
235
|
if (generation !== gen || !isLive()) return
|
|
236
|
-
if (
|
|
236
|
+
if (provider.startPresence)
|
|
237
237
|
try {
|
|
238
238
|
const stop = await provider.startPresence(async () => ({
|
|
239
239
|
nodeHash: localIdentity.nodeHash,
|
|
240
240
|
advertBytes: await buildNetworkAdvertBytes(),
|
|
241
241
|
}))
|
|
242
|
-
if (
|
|
242
|
+
if (stop && generation === gen && isLive()) {
|
|
243
243
|
const prev = stopPresence
|
|
244
244
|
/**
|
|
245
245
|
* 停止 presence 广播并链式调用上一轮清理。
|
|
@@ -248,12 +248,12 @@ export function createRuntimeBootstrap(deps) {
|
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
250
|
catch { /* ignore */ }
|
|
251
|
-
if (provider.caps?.canSignal &&
|
|
251
|
+
if (provider.caps?.canSignal && provider.listenNodeSignals)
|
|
252
252
|
try {
|
|
253
253
|
const stop = await provider.listenNodeSignals(localIdentity.nodeHash, bytes => {
|
|
254
254
|
void handleIncomingSignal(bytes).catch(() => { })
|
|
255
255
|
})
|
|
256
|
-
if (
|
|
256
|
+
if (stop && generation === gen && isLive()) {
|
|
257
257
|
const prev = stopSignalListener
|
|
258
258
|
/**
|
|
259
259
|
* 停止信令监听并链式调用上一轮清理。
|
package/trust_graph/cache.mjs
CHANGED
|
@@ -22,10 +22,9 @@ export function invalidateTrustGraphCache() {
|
|
|
22
22
|
* @returns {Promise<Map<string, object>>} 合并后的信任图
|
|
23
23
|
*/
|
|
24
24
|
export async function getCachedTrustGraph(username, build, ttlMs = DEFAULT_TTL_MS) {
|
|
25
|
-
const key = String(username || '')
|
|
26
25
|
const now = Date.now()
|
|
27
26
|
const dataRevision = getLocalDataRevision()
|
|
28
|
-
const cached = cacheByUsername.get(
|
|
27
|
+
const cached = cacheByUsername.get(username)
|
|
29
28
|
if (
|
|
30
29
|
cached
|
|
31
30
|
&& cached.revision === revision
|
|
@@ -35,6 +34,6 @@ export async function getCachedTrustGraph(username, build, ttlMs = DEFAULT_TTL_M
|
|
|
35
34
|
return cached.graph
|
|
36
35
|
|
|
37
36
|
const graph = await build()
|
|
38
|
-
cacheByUsername.set(
|
|
37
|
+
cacheByUsername.set(username, { graph, builtAt: now, revision, dataRevision })
|
|
39
38
|
return graph
|
|
40
39
|
}
|
package/trust_graph/engine.mjs
CHANGED
|
@@ -89,7 +89,7 @@ export function mergeGraph(inputs, tunables = trustGraphTunables) {
|
|
|
89
89
|
const rawWeight = Number(hint.weight ?? tunables.hintDefaultWeight)
|
|
90
90
|
if (Number.isFinite(rawWeight) && rawWeight > 0)
|
|
91
91
|
ev.hintWeightSum += rawWeight
|
|
92
|
-
ev.hintSources.add(
|
|
92
|
+
ev.hintSources.add(hint.source ?? 'unknown')
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
for (const room of inputs.roomRosters || [])
|
package/trust_graph/registry.mjs
CHANGED
|
@@ -13,7 +13,7 @@ export const DEFAULT_TRUST_GRAPH_OWNER = 'default'
|
|
|
13
13
|
* @returns {void}
|
|
14
14
|
*/
|
|
15
15
|
export function registerTrustGraphProvider(ownerId, implementation) {
|
|
16
|
-
providersByOwner.set(
|
|
16
|
+
providersByOwner.set(ownerId, implementation)
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/** @returns {void} */
|
|
@@ -26,7 +26,7 @@ export function clearTrustGraphProvider() {
|
|
|
26
26
|
* @returns {import('./registry.mjs').TrustGraphProvider} 已注册实现
|
|
27
27
|
*/
|
|
28
28
|
export function requireTrustGraphProvider(ownerId = DEFAULT_TRUST_GRAPH_OWNER) {
|
|
29
|
-
const implementation = providersByOwner.get(
|
|
29
|
+
const implementation = providersByOwner.get(ownerId)
|
|
30
30
|
if (!implementation)
|
|
31
31
|
throw new Error(`p2p: registerTrustGraphProvider('${ownerId}') must run before trust graph fanout`)
|
|
32
32
|
return implementation
|
package/trust_graph/send.mjs
CHANGED
|
@@ -20,7 +20,7 @@ import trustGraphTunables from './tunables.json' with { type: 'json' }
|
|
|
20
20
|
* @returns {Promise<boolean>} 是否已发送
|
|
21
21
|
*/
|
|
22
22
|
export async function sendToNode(username, targetNodeHash, actionName, payload, graph) {
|
|
23
|
-
const target = normalizeHex64(targetNodeHash)
|
|
23
|
+
const target = normalizeHex64(targetNodeHash)
|
|
24
24
|
if (!isHex64(target)) return false
|
|
25
25
|
|
|
26
26
|
// 已直连 peer 不经 trust-graph scope 也应能收发 node scope action(非成员 CAS chunk / follow hint 等)
|
package/utils/async_mutex.mjs
CHANGED
|
@@ -39,7 +39,7 @@ function releaseMutex(lockKey, state) {
|
|
|
39
39
|
* @template T
|
|
40
40
|
*/
|
|
41
41
|
export async function withAsyncMutex(key, criticalSection) {
|
|
42
|
-
const lockKey =
|
|
42
|
+
const lockKey = key
|
|
43
43
|
const state = mutexState(lockKey)
|
|
44
44
|
return new Promise((resolve, reject) => {
|
|
45
45
|
/**
|
package/wire/ingress.mjs
CHANGED
|
@@ -46,6 +46,6 @@ export function isSignedDagEventRow(event) {
|
|
|
46
46
|
*/
|
|
47
47
|
export function extractInboundSignedEvent(payload, groupId) {
|
|
48
48
|
if (!isSignedDagEventRow(payload)) return null
|
|
49
|
-
if (payload.groupId &&
|
|
49
|
+
if (payload.groupId && payload.groupId !== groupId) return null
|
|
50
50
|
return payload
|
|
51
51
|
}
|
package/wire/part/ingress.mjs
CHANGED
|
@@ -21,15 +21,15 @@ import { handleIncomingPartInvokeResponse } from './pending.mjs'
|
|
|
21
21
|
* @returns {object | null} 白名单字段
|
|
22
22
|
*/
|
|
23
23
|
function parsePartTimelinePut(data, partpath) {
|
|
24
|
-
const timelineEntityHash = String(data.timelineEntityHash || '')
|
|
24
|
+
const timelineEntityHash = String(data.timelineEntityHash || '')
|
|
25
25
|
if (!timelineEntityHash || !isPlainObject(data.event)) return null
|
|
26
26
|
return {
|
|
27
27
|
type: 'part_timeline_put',
|
|
28
28
|
partpath,
|
|
29
29
|
timelineEntityHash,
|
|
30
30
|
event: data.event,
|
|
31
|
-
...data.nodeHash ? { nodeHash: String(data.nodeHash)
|
|
32
|
-
...data.groupId ? { groupId: String(data.groupId)
|
|
31
|
+
...data.nodeHash ? { nodeHash: String(data.nodeHash) } : {},
|
|
32
|
+
...data.groupId ? { groupId: String(data.groupId) } : {},
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -60,8 +60,8 @@ async function dispatchPartInvoke(wireContext, payload) {
|
|
|
60
60
|
if (!partpath || !isPlainObject(invoke)) return null
|
|
61
61
|
return dispatchRpcInbound({
|
|
62
62
|
replicaUsername: wireContext.replicaUsername,
|
|
63
|
-
requesterNodeHash: payload.peerId
|
|
64
|
-
groupId: payload.groupId
|
|
63
|
+
requesterNodeHash: payload.peerId || null,
|
|
64
|
+
groupId: payload.groupId || undefined,
|
|
65
65
|
peerId: payload.peerId,
|
|
66
66
|
}, {
|
|
67
67
|
type: 'part_invoke',
|
|
@@ -93,7 +93,7 @@ export function attachPartWire(wireContext, wire, options = {}) {
|
|
|
93
93
|
if (!message) return
|
|
94
94
|
void dispatchDeliveryInbound({
|
|
95
95
|
replicaUsername: wireContext.replicaUsername,
|
|
96
|
-
requesterNodeHash: peerId
|
|
96
|
+
requesterNodeHash: peerId || null,
|
|
97
97
|
}, message)
|
|
98
98
|
},
|
|
99
99
|
/**
|
package/wire/part/query.mjs
CHANGED
|
@@ -48,11 +48,11 @@ export function attachPartQueryWire(wireContext, wire, dependencies = {}) {
|
|
|
48
48
|
const request = parsePartQueryReq(data)
|
|
49
49
|
if (!request) return
|
|
50
50
|
if (!state.takeDedupe(request.requestId)) return
|
|
51
|
-
const source =
|
|
51
|
+
const source = peerId || request.originNodeHash
|
|
52
52
|
if (source && !consumeWireRateBucket(`part_query:${source}`, {
|
|
53
53
|
maxCount: partQueryTunables.ratePerSourcePerMin,
|
|
54
54
|
})) return
|
|
55
|
-
void processIncomingPartQueryRequest(wireContext, wire, request,
|
|
55
|
+
void processIncomingPartQueryRequest(wireContext, wire, request, peerId, deps)
|
|
56
56
|
},
|
|
57
57
|
/**
|
|
58
58
|
* @param {unknown} data part_query_res 载荷
|
|
@@ -61,7 +61,7 @@ export function attachPartQueryWire(wireContext, wire, dependencies = {}) {
|
|
|
61
61
|
part_query_res(data, peerId) {
|
|
62
62
|
const response = parsePartQueryRes(data)
|
|
63
63
|
if (!response) return
|
|
64
|
-
handleIncomingPartQueryResponse(response,
|
|
64
|
+
handleIncomingPartQueryResponse(response, peerId, deps)
|
|
65
65
|
},
|
|
66
66
|
})
|
|
67
67
|
}
|
|
@@ -28,10 +28,9 @@ export function boundStreamSlices(slices) {
|
|
|
28
28
|
* @returns {Uint8Array} 验签消息字节
|
|
29
29
|
*/
|
|
30
30
|
export function streamChunkSignBytes(pendingStreamId, chunkSeq, slices) {
|
|
31
|
-
const streamId = String(pendingStreamId ?? '')
|
|
32
31
|
const sequence = Number.isFinite(Number(chunkSeq)) ? String(chunkSeq) : '0'
|
|
33
32
|
const body = JSON.stringify(slices ?? [])
|
|
34
|
-
return new TextEncoder().encode(`${CHUNK_DOMAIN}\0${
|
|
33
|
+
return new TextEncoder().encode(`${CHUNK_DOMAIN}\0${pendingStreamId}\0${sequence}\0${body}`)
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
/**
|
|
@@ -51,11 +50,9 @@ export async function signStreamSignatureHex(bytes, secretKey) {
|
|
|
51
50
|
* @returns {Promise<boolean>} 验签是否通过
|
|
52
51
|
*/
|
|
53
52
|
export async function verifyStreamSignatureHex(bytes, signatureHex, publicKeyHex) {
|
|
54
|
-
|
|
55
|
-
const publicKey = String(publicKeyHex || '').trim().toLowerCase()
|
|
56
|
-
if (!isSignatureHex128(signature) || !isHex64(publicKey)) return false
|
|
53
|
+
if (!isSignatureHex128(signatureHex) || !isHex64(publicKeyHex)) return false
|
|
57
54
|
try {
|
|
58
|
-
return await verify(Buffer.from(
|
|
55
|
+
return await verify(Buffer.from(signatureHex, 'hex'), bytes, Buffer.from(publicKeyHex, 'hex'))
|
|
59
56
|
}
|
|
60
57
|
catch {
|
|
61
58
|
return false
|
|
@@ -69,8 +66,6 @@ export async function verifyStreamSignatureHex(bytes, signatureHex, publicKeyHex
|
|
|
69
66
|
* @returns {{ publicKey: Uint8Array, secretKey: Uint8Array }} 密钥对
|
|
70
67
|
*/
|
|
71
68
|
export function streamKeyPairFromUserSeed(username, seedMaterial) {
|
|
72
|
-
const
|
|
73
|
-
const material = String(seedMaterial ?? '')
|
|
74
|
-
const seed = createHash('sha256').update(`fount-stream-sign\0${user}\0${material}`).digest()
|
|
69
|
+
const seed = createHash('sha256').update(`fount-stream-sign\0${username}\0${seedMaterial}`).digest()
|
|
75
70
|
return keyPairFromSeed(seed)
|
|
76
71
|
}
|