@steve02081504/fount-p2p 0.0.26 → 0.0.27
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 +1 -0
- 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 +3 -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 +16 -21
- 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 +2 -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 +1 -2
- 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 +1 -1
- 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 +2 -2
- package/transport/ice_servers.mjs +2 -2
- package/transport/link_registry.mjs +6 -18
- package/transport/mesh_keepalive.mjs +1 -1
- package/transport/node_scope/wire.mjs +1 -1
- 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
package/reputation/engine.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 信誉纯内存算子(模拟器与磁盘 store 共用)。
|
|
3
3
|
*/
|
|
4
|
+
import { normalizeHex64 } from '../core/hexIds.mjs'
|
|
5
|
+
|
|
4
6
|
import {
|
|
5
7
|
clampReputationScore,
|
|
6
8
|
computeRepMaxEff,
|
|
@@ -176,9 +178,9 @@ export function pruneReputationFile(data, tunables = reputationTunables, now = D
|
|
|
176
178
|
* @returns {boolean} 是否已加分
|
|
177
179
|
*/
|
|
178
180
|
export function bumpReputationOnRelayPure(data, peerNodeHash, dedupeKey, now = Date.now(), tunables = reputationTunables) {
|
|
179
|
-
const id =
|
|
181
|
+
const id = normalizeHex64(peerNodeHash)
|
|
180
182
|
if (!id) return false
|
|
181
|
-
const key =
|
|
183
|
+
const key = dedupeKey || `conn:${id}`
|
|
182
184
|
data.relayBumpSeen = data.relayBumpSeen.filter(hit => now - hit.t <= tunables.relayBumpDedupeMs)
|
|
183
185
|
if (relayBumpIsDuplicate(data.relayBumpSeen, id, key, now, tunables.relayBumpDedupeMs)) return false
|
|
184
186
|
data.relayBumpSeen.push({ peerNodeHash: id, key, t: now })
|
|
@@ -213,7 +215,7 @@ export function recordGossipAllUnknownWantPure(data, peerNodeHash, now = Date.no
|
|
|
213
215
|
* @returns {void}
|
|
214
216
|
*/
|
|
215
217
|
export function recordMessageRateViolationPure(data, peerNodeHash, tunables = reputationTunables, excessRatio = 1) {
|
|
216
|
-
const id =
|
|
218
|
+
const id = normalizeHex64(peerNodeHash)
|
|
217
219
|
if (!id) return
|
|
218
220
|
const ratio = Math.max(0, Math.min(1, Number(excessRatio) || 0))
|
|
219
221
|
if (ratio <= 0) return
|
|
@@ -227,7 +229,7 @@ export function recordMessageRateViolationPure(data, peerNodeHash, tunables = re
|
|
|
227
229
|
* @returns {void}
|
|
228
230
|
*/
|
|
229
231
|
export function bumpChunkStorageReputationPure(data, storagePeerKey, tunables = reputationTunables) {
|
|
230
|
-
const id =
|
|
232
|
+
const id = normalizeHex64(storagePeerKey)
|
|
231
233
|
if (!id) return
|
|
232
234
|
adjustNodeReputation(data, id, tunables.chunkStoreRepBump)
|
|
233
235
|
}
|
|
@@ -239,7 +241,7 @@ export function bumpChunkStorageReputationPure(data, storagePeerKey, tunables =
|
|
|
239
241
|
* @returns {void}
|
|
240
242
|
*/
|
|
241
243
|
export function penalizeChunkStorageFailurePure(data, blamePeerKey, tunables = reputationTunables) {
|
|
242
|
-
const id =
|
|
244
|
+
const id = normalizeHex64(blamePeerKey)
|
|
243
245
|
if (!id) return
|
|
244
246
|
adjustNodeReputation(data, id, -tunables.chunkFetchFailPenalty)
|
|
245
247
|
}
|
|
@@ -251,7 +253,7 @@ export function penalizeChunkStorageFailurePure(data, blamePeerKey, tunables = r
|
|
|
251
253
|
* @returns {void}
|
|
252
254
|
*/
|
|
253
255
|
export function penalizeArchiveServeMismatchPure(data, peerNodeHash, tunables = reputationTunables) {
|
|
254
|
-
const id =
|
|
256
|
+
const id = normalizeHex64(peerNodeHash)
|
|
255
257
|
if (!id) return
|
|
256
258
|
adjustNodeReputation(data, id, -tunables.archiveServeMismatchPenalty)
|
|
257
259
|
}
|
|
@@ -280,7 +282,7 @@ export function applySubjectiveSlashPure(data, target, sender, claim, verified,
|
|
|
280
282
|
* @returns {Array<{ hop: number, node: string, dRep: number }>} 已应用的上游惩罚
|
|
281
283
|
*/
|
|
282
284
|
export function applyDecayCollusionAfterSlashPure(data, targetPubKeyHash, inviteEdges, tunables = reputationTunables) {
|
|
283
|
-
const target = targetPubKeyHash
|
|
285
|
+
const target = normalizeHex64(targetPubKeyHash)
|
|
284
286
|
/** @type {Array<{ hop: number, node: string, dRep: number }>} */
|
|
285
287
|
const applied = []
|
|
286
288
|
let frontier = new Set([target])
|
|
@@ -301,8 +303,8 @@ export function applyDecayCollusionAfterSlashPure(data, targetPubKeyHash, invite
|
|
|
301
303
|
for (let hop = 1; hop <= effectiveMaxHop(); hop++) {
|
|
302
304
|
const upstream = new Set()
|
|
303
305
|
for (const edge of inviteEdges) {
|
|
304
|
-
const to =
|
|
305
|
-
const from =
|
|
306
|
+
const to = normalizeHex64(edge.to)
|
|
307
|
+
const from = normalizeHex64(edge.from)
|
|
306
308
|
if (frontier.has(to) && from) upstream.add(from)
|
|
307
309
|
}
|
|
308
310
|
if (!upstream.size) break
|
|
@@ -331,7 +333,7 @@ export function applyDecayCollusionAfterSlashPure(data, targetPubKeyHash, invite
|
|
|
331
333
|
* @returns {void}
|
|
332
334
|
*/
|
|
333
335
|
export function applyReputationResetToScoresPure(data, targetPubKeyHash) {
|
|
334
|
-
const t = targetPubKeyHash
|
|
336
|
+
const t = normalizeHex64(targetPubKeyHash)
|
|
335
337
|
const row = repRow(data, t)
|
|
336
338
|
row.score = 0
|
|
337
339
|
delete row.offenseStreak
|
|
@@ -351,8 +353,8 @@ export function applyReputationResetToScoresPure(data, targetPubKeyHash) {
|
|
|
351
353
|
* @returns {void}
|
|
352
354
|
*/
|
|
353
355
|
export function seedMemberReputationFromIntroducerPure(data, memberPubKeyHash, introducerPubKeyHash, repEdge, tunables = reputationTunables, powBonus = 0) {
|
|
354
|
-
const memberKey = memberPubKeyHash
|
|
355
|
-
const introducerKey = introducerPubKeyHash
|
|
356
|
+
const memberKey = normalizeHex64(memberPubKeyHash)
|
|
357
|
+
const introducerKey = normalizeHex64(introducerPubKeyHash)
|
|
356
358
|
if (data.byNodeHash[memberKey]) return
|
|
357
359
|
const introducerReputation = introducerKey
|
|
358
360
|
? Number(data.byNodeHash[introducerKey]?.score ?? 0)
|
|
@@ -392,7 +394,7 @@ function baselineRow(row) {
|
|
|
392
394
|
* @returns {{ z: number, anomaly: boolean }} 偏离度
|
|
393
395
|
*/
|
|
394
396
|
export function observeBehaviorSamplePure(data, peerNodeHash, sample, now = Date.now(), tunables = reputationTunables) {
|
|
395
|
-
const id =
|
|
397
|
+
const id = normalizeHex64(peerNodeHash)
|
|
396
398
|
if (!id) return { z: 0, anomaly: false }
|
|
397
399
|
const row = repRow(data, id)
|
|
398
400
|
const baseline = baselineRow(row)
|
|
@@ -427,7 +429,7 @@ export function observeBehaviorSamplePure(data, peerNodeHash, sample, now = Date
|
|
|
427
429
|
* @returns {void}
|
|
428
430
|
*/
|
|
429
431
|
export function applyQuarantinePure(data, peerNodeHash, now = Date.now(), tunables = reputationTunables) {
|
|
430
|
-
const id =
|
|
432
|
+
const id = normalizeHex64(peerNodeHash)
|
|
431
433
|
if (!id) return
|
|
432
434
|
const row = repRow(data, id)
|
|
433
435
|
row.quarantinedUntil = now + Number(tunables.quarantineTtlMs ?? 900_000)
|
|
@@ -441,7 +443,7 @@ export function applyQuarantinePure(data, peerNodeHash, now = Date.now(), tunabl
|
|
|
441
443
|
* @returns {boolean} 是否处于本地隔离
|
|
442
444
|
*/
|
|
443
445
|
export function isQuarantinedPure(data, peerNodeHash, now = Date.now()) {
|
|
444
|
-
const id =
|
|
446
|
+
const id = normalizeHex64(peerNodeHash)
|
|
445
447
|
const until = Number(data.byNodeHash?.[id]?.quarantinedUntil ?? 0)
|
|
446
448
|
return Number.isFinite(until) && until > now
|
|
447
449
|
}
|
package/schemas/discovery.mjs
CHANGED
|
@@ -9,9 +9,9 @@ const MAX_DISCOVERY_ADS = 64
|
|
|
9
9
|
*/
|
|
10
10
|
function sanitizeDiscoveryAdvertisement(ad) {
|
|
11
11
|
if (!isPlainObject(ad)) return null
|
|
12
|
-
const groupId = String(ad.groupId || '')
|
|
12
|
+
const groupId = String(ad.groupId || '')
|
|
13
13
|
const advertiserPubKeyHash = normalizeHex64(ad.advertiserPubKeyHash)
|
|
14
|
-
const signature = String(ad.signature || '')
|
|
14
|
+
const signature = String(ad.signature || '')
|
|
15
15
|
if (!groupId || !isHex64(advertiserPubKeyHash) || !isSignatureHex128(signature)) return null
|
|
16
16
|
const advertiserNodeHash = normalizeHex64(ad.advertiserNodeHash)
|
|
17
17
|
const body = {
|
|
@@ -19,7 +19,7 @@ function sanitizeDiscoveryAdvertisement(ad) {
|
|
|
19
19
|
title: String(ad.title || '').slice(0, 200),
|
|
20
20
|
blurb: String(ad.blurb || '').slice(0, 500),
|
|
21
21
|
advertiserPubKeyHash,
|
|
22
|
-
advertiserNodeHash: isHex64(advertiserNodeHash) ? advertiserNodeHash : String(ad.advertiserNodeHash || '')
|
|
22
|
+
advertiserNodeHash: isHex64(advertiserNodeHash) ? advertiserNodeHash : String(ad.advertiserNodeHash || ''),
|
|
23
23
|
observedAt: Number(ad.observedAt) || 0,
|
|
24
24
|
signature,
|
|
25
25
|
}
|
|
@@ -47,10 +47,10 @@ export function assertDiscoveryNodeHash(nodeHash) {
|
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
49
|
* @param {unknown} requestId 请求 id
|
|
50
|
-
* @returns {string}
|
|
50
|
+
* @returns {string} 非空字符串
|
|
51
51
|
*/
|
|
52
52
|
export function assertDiscoveryRequestId(requestId) {
|
|
53
|
-
const id = String(requestId ?? '')
|
|
53
|
+
const id = String(requestId ?? '')
|
|
54
54
|
if (!id) throw new Error('discovery.requestId required')
|
|
55
55
|
return id
|
|
56
56
|
}
|
|
@@ -14,7 +14,7 @@ const ENVELOPE_BLOB_MIN_LEN = 2
|
|
|
14
14
|
* @returns {boolean} 是否像合法 ECIES blob 段
|
|
15
15
|
*/
|
|
16
16
|
function isEnvelopeBlob(value) {
|
|
17
|
-
return String(value ?? '').
|
|
17
|
+
return String(value ?? '').length >= ENVELOPE_BLOB_MIN_LEN
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/**
|
|
@@ -24,14 +24,14 @@ function isEnvelopeBlob(value) {
|
|
|
24
24
|
export function parsePullAttestation(attestation) {
|
|
25
25
|
if (!isPlainObject(attestation)) return null
|
|
26
26
|
const requesterPubKeyHash = normalizeHex64(attestation.requesterPubKeyHash)
|
|
27
|
-
const groupId = String(attestation.groupId || '')
|
|
28
|
-
const requestId = String(attestation.requestId || '')
|
|
27
|
+
const groupId = String(attestation.groupId || '')
|
|
28
|
+
const requestId = String(attestation.requestId || '')
|
|
29
29
|
const timestamp = Number(attestation.timestamp)
|
|
30
|
-
const signature = String(attestation.signature || '')
|
|
30
|
+
const signature = String(attestation.signature || '')
|
|
31
31
|
if (!isHex64(requesterPubKeyHash) || !groupId || !Number.isFinite(timestamp) || !isSignatureHex128(signature))
|
|
32
32
|
return null
|
|
33
33
|
const wantIds = attestation.wantIds?.length
|
|
34
|
-
? [...new Set(attestation.wantIds.map(id => id
|
|
34
|
+
? [...new Set(attestation.wantIds.map(id => normalizeHex64(id)).filter(id => EVENT_ID_HEX.test(id)))]
|
|
35
35
|
: undefined
|
|
36
36
|
return {
|
|
37
37
|
requesterPubKeyHash,
|
|
@@ -49,13 +49,13 @@ export function parsePullAttestation(attestation) {
|
|
|
49
49
|
*/
|
|
50
50
|
export function parsePullResponseEnvelope(envelope) {
|
|
51
51
|
if (!isPlainObject(envelope)) return null
|
|
52
|
-
const requestId = String(envelope.requestId || '')
|
|
52
|
+
const requestId = String(envelope.requestId || '')
|
|
53
53
|
const requesterPubKeyHash = normalizeHex64(envelope.requesterPubKeyHash)
|
|
54
|
-
const requesterNodeHash = String(envelope.requesterNodeHash || '')
|
|
55
|
-
const ephemPub = String(envelope.ephemPub || '')
|
|
56
|
-
const iv = String(envelope.iv || '')
|
|
57
|
-
const ciphertext = String(envelope.ciphertext || '')
|
|
58
|
-
const authTag = String(envelope.authTag || '')
|
|
54
|
+
const requesterNodeHash = String(envelope.requesterNodeHash || '')
|
|
55
|
+
const ephemPub = String(envelope.ephemPub || '')
|
|
56
|
+
const iv = String(envelope.iv || '')
|
|
57
|
+
const ciphertext = String(envelope.ciphertext || '')
|
|
58
|
+
const authTag = String(envelope.authTag || '')
|
|
59
59
|
if (!requestId || !isHex64(requesterPubKeyHash) || !requesterNodeHash) return null
|
|
60
60
|
if (!isEnvelopeBlob(ephemPub) || !isEnvelopeBlob(iv) || !isEnvelopeBlob(ciphertext) || !isEnvelopeBlob(authTag))
|
|
61
61
|
return null
|
|
@@ -76,27 +76,19 @@ export function parsePullResponseEnvelope(envelope) {
|
|
|
76
76
|
*/
|
|
77
77
|
export function parseJoinSnapshotRequest(data) {
|
|
78
78
|
if (!isPlainObject(data)) return null
|
|
79
|
-
const requestId = String(data.requestId || '')
|
|
80
|
-
const requesterNodeHash = String(data.requesterNodeHash || '')
|
|
81
|
-
const groupId = String(data.groupId || '')
|
|
79
|
+
const requestId = String(data.requestId || '')
|
|
80
|
+
const requesterNodeHash = String(data.requesterNodeHash || '')
|
|
81
|
+
const groupId = String(data.groupId || '')
|
|
82
82
|
const attestation = parsePullAttestation(data.attestation)
|
|
83
83
|
if (!requestId || !requesterNodeHash || !groupId || !attestation) return null
|
|
84
84
|
if (attestation.groupId !== groupId || attestation.requestId !== requestId) return null
|
|
85
|
-
const tipsHash = String(data.tipsHash || '')
|
|
85
|
+
const tipsHash = String(data.tipsHash || '')
|
|
86
86
|
return {
|
|
87
87
|
requestId,
|
|
88
88
|
requesterNodeHash,
|
|
89
89
|
requesterPubKeyHash: attestation.requesterPubKeyHash,
|
|
90
90
|
groupId,
|
|
91
|
-
tipsHash: tipsHash
|
|
91
|
+
tipsHash: tipsHash || undefined,
|
|
92
92
|
attestation,
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* @param {unknown} data 入群快照响应载荷
|
|
98
|
-
* @returns {object | null} 解析结果
|
|
99
|
-
*/
|
|
100
|
-
export function parseJoinSnapshotResponse(data) {
|
|
101
|
-
return parsePullResponseEnvelope(data)
|
|
102
|
-
}
|
package/schemas/part_query.mjs
CHANGED
|
@@ -82,10 +82,10 @@ const REQUEST_ID_MAX_LENGTH = 128
|
|
|
82
82
|
|
|
83
83
|
/**
|
|
84
84
|
* @param {unknown} value 原始 requestId
|
|
85
|
-
* @returns {string | null}
|
|
85
|
+
* @returns {string | null} 合法 requestId;非法 null
|
|
86
86
|
*/
|
|
87
87
|
function normalizeRequestId(value) {
|
|
88
|
-
const requestId = String(value || '')
|
|
88
|
+
const requestId = String(value || '')
|
|
89
89
|
if (!requestId || requestId.length > REQUEST_ID_MAX_LENGTH) return null
|
|
90
90
|
return requestId
|
|
91
91
|
}
|
|
@@ -103,7 +103,7 @@ export function parsePartQueryReq(value, tunables = partQueryTunables) {
|
|
|
103
103
|
if (!isHex64(originNodeHash)) return null
|
|
104
104
|
const partpath = parsePartpath(value.partpath)
|
|
105
105
|
if (!partpath) return null
|
|
106
|
-
const kind = String(value.kind || '')
|
|
106
|
+
const kind = String(value.kind || '')
|
|
107
107
|
if (!kind) return null
|
|
108
108
|
if (!Object.prototype.hasOwnProperty.call(value, 'query')) return null
|
|
109
109
|
if (measureJsonBytes(value.query) > (tunables.maxQueryBytes ?? 2048)) return null
|
|
@@ -145,7 +145,7 @@ export function parsePartQueryRes(value, tunables = partQueryTunables) {
|
|
|
145
145
|
*/
|
|
146
146
|
export function normalizePartQueryCacheMaterial(partpath, kind, query) {
|
|
147
147
|
const path = parsePartpath(partpath)
|
|
148
|
-
const k = String(kind || '')
|
|
148
|
+
const k = String(kind || '')
|
|
149
149
|
if (!path || !k) return null
|
|
150
150
|
if (measureJsonBytes(query) > partQueryTunables.maxQueryBytes) return null
|
|
151
151
|
try {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { Buffer } from 'node:buffer'
|
|
5
5
|
|
|
6
|
-
import { isHex64, isSignatureHex128 } from '../core/hexIds.mjs'
|
|
6
|
+
import { isHex64, isSignatureHex128, normalizeHex64 } from '../core/hexIds.mjs'
|
|
7
7
|
import { pubKeyHash, verify } from '../crypto/crypto.mjs'
|
|
8
8
|
import { eventBodyForSign, signPayloadBytes } from '../dag/index.mjs'
|
|
9
9
|
|
|
@@ -12,11 +12,11 @@ import { eventBodyForSign, signPayloadBytes } from '../dag/index.mjs'
|
|
|
12
12
|
* @returns {Promise<boolean>} 验签是否通过
|
|
13
13
|
*/
|
|
14
14
|
export async function verifyTimelineRemoteSignature(event) {
|
|
15
|
-
const sender =
|
|
15
|
+
const sender = normalizeHex64(event?.sender)
|
|
16
16
|
if (!isHex64(sender)) return false
|
|
17
|
-
const signatureHex = String(event?.signature || '')
|
|
17
|
+
const signatureHex = String(event?.signature || '')
|
|
18
18
|
if (!isSignatureHex128(signatureHex)) return false
|
|
19
|
-
const pubKeyHex =
|
|
19
|
+
const pubKeyHex = normalizeHex64(event?.senderPubKey)
|
|
20
20
|
if (!isHex64(pubKeyHex)) return false
|
|
21
21
|
const publicKeyBytes = new Uint8Array(Buffer.from(pubKeyHex, 'hex'))
|
|
22
22
|
if (pubKeyHash(publicKeyBytes) !== sender) return false
|
|
@@ -59,7 +59,7 @@ export function createGroupLinkSet(options) {
|
|
|
59
59
|
* @returns {void}
|
|
60
60
|
*/
|
|
61
61
|
function registerCleanup(cleanup) {
|
|
62
|
-
if (
|
|
62
|
+
if (!cleanup) return
|
|
63
63
|
cleanups.add(cleanup)
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -182,7 +182,7 @@ export function createGroupLinkSet(options) {
|
|
|
182
182
|
}
|
|
183
183
|
started = true
|
|
184
184
|
active = true
|
|
185
|
-
if (
|
|
185
|
+
if (registry.ensureRuntime)
|
|
186
186
|
await registry.ensureRuntime()
|
|
187
187
|
registry.registerScopeInterest(scope, [...members])
|
|
188
188
|
registerCleanup(registry.subscribeScope(scope, (senderNodeHash, envelope) => {
|
|
@@ -27,8 +27,8 @@ function normalizeIceEntry(raw) {
|
|
|
27
27
|
if (!raw || typeof raw !== 'object') return null
|
|
28
28
|
const urlsRaw = raw.urls
|
|
29
29
|
const urlsList = Array.isArray(urlsRaw)
|
|
30
|
-
? urlsRaw.map(u => String(u)
|
|
31
|
-
: [String(urlsRaw || '')
|
|
30
|
+
? urlsRaw.map(u => String(u || '')).filter(Boolean)
|
|
31
|
+
: [String(urlsRaw || '')].filter(Boolean)
|
|
32
32
|
if (!urlsList.length) return null
|
|
33
33
|
for (const u of urlsList)
|
|
34
34
|
if (!ICE_URL_RE.test(u)) return null
|
|
@@ -293,14 +293,14 @@ export function createLinkRegistry(options = {}) {
|
|
|
293
293
|
const providers = listLinkProviders()
|
|
294
294
|
for (const provider of providers)
|
|
295
295
|
try {
|
|
296
|
-
if (
|
|
296
|
+
if (provider.canReach) {
|
|
297
297
|
const reachable = await Promise.resolve(provider.canReach({ nodeHash: normalized }))
|
|
298
298
|
if (!reachable) {
|
|
299
299
|
nodeDebug('p2p:dial skip', { peer: shortHash(normalized), provider: provider.id, reason: 'canReach=false' })
|
|
300
300
|
continue
|
|
301
301
|
}
|
|
302
302
|
}
|
|
303
|
-
if (
|
|
303
|
+
if (provider.isAvailable) {
|
|
304
304
|
const available = await Promise.resolve(provider.isAvailable())
|
|
305
305
|
if (!available) {
|
|
306
306
|
nodeDebug('p2p:dial skip', { peer: shortHash(normalized), provider: provider.id, reason: 'isAvailable=false' })
|
|
@@ -470,17 +470,6 @@ export function createLinkRegistry(options = {}) {
|
|
|
470
470
|
|| await relayEnvelopeToNode(remoteNodeHash, envelope)
|
|
471
471
|
}
|
|
472
472
|
|
|
473
|
-
/**
|
|
474
|
-
* 同步结果直接返回;thenable 才 await(避免每条 envelope 造 microtask)。
|
|
475
|
-
* @param {unknown} value 可能为 Promise 的返回值
|
|
476
|
-
* @returns {Promise<unknown>} 已 resolve 的值
|
|
477
|
-
*/
|
|
478
|
-
async function maybeAwait(value) {
|
|
479
|
-
if (value != null && typeof /** @type {{ then?: unknown }} */ value.then === 'function')
|
|
480
|
-
return await value
|
|
481
|
-
return value
|
|
482
|
-
}
|
|
483
|
-
|
|
484
473
|
/**
|
|
485
474
|
* 将入站 envelope 派发到 scope 监听器(经 authorizer 校验)。
|
|
486
475
|
* @param {string} senderNodeHash 发送方节点 64 hex
|
|
@@ -489,16 +478,15 @@ export function createLinkRegistry(options = {}) {
|
|
|
489
478
|
* @returns {Promise<void>}
|
|
490
479
|
*/
|
|
491
480
|
async function dispatchEnvelope(senderNodeHash, envelope, link) {
|
|
492
|
-
const scope = envelope
|
|
481
|
+
const { scope } = envelope
|
|
493
482
|
for (const [prefix, authorizer] of scopeAuthorizers.entries())
|
|
494
483
|
if (scope.startsWith(prefix)) {
|
|
495
|
-
|
|
496
|
-
if (!allowed) return
|
|
484
|
+
if (!await authorizer(scope, senderNodeHash, envelope, link)) return
|
|
497
485
|
}
|
|
498
486
|
for (const [prefix, listeners] of scopeListeners.entries())
|
|
499
487
|
if (scope.startsWith(prefix))
|
|
500
488
|
for (const listener of listeners)
|
|
501
|
-
await
|
|
489
|
+
await listener(senderNodeHash, envelope, link)
|
|
502
490
|
}
|
|
503
491
|
|
|
504
492
|
/**
|
|
@@ -567,7 +555,7 @@ export function createLinkRegistry(options = {}) {
|
|
|
567
555
|
* @returns {void}
|
|
568
556
|
*/
|
|
569
557
|
setPriorityWeightFunction(weightFunction) {
|
|
570
|
-
priorityWeightFunction =
|
|
558
|
+
priorityWeightFunction = weightFunction || null
|
|
571
559
|
},
|
|
572
560
|
/**
|
|
573
561
|
* @param {string} nodeHash - 节点 64-hex hash
|
|
@@ -103,7 +103,7 @@ export function getNodeScopeContext() {
|
|
|
103
103
|
*/
|
|
104
104
|
export function ensureNodeScope(options = {}) {
|
|
105
105
|
if (options.replicaUsername != null)
|
|
106
|
-
nodeScopeContext.replicaUsername =
|
|
106
|
+
nodeScopeContext.replicaUsername = options.replicaUsername
|
|
107
107
|
if (nodeScopeSubscribeCleanup) return nodeScopeSubscribeCleanup
|
|
108
108
|
nodeScopeSubscribeCleanup = subscribeScope('node', (senderNodeHash, envelope) => {
|
|
109
109
|
const handlers = nodeActionHandlers.get(envelope.action)
|
|
@@ -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.id
|
|
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
|