@steve02081504/fount-p2p 0.0.35 → 0.0.36
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/AGENTS.md +1 -1
- package/core/entity_id.mjs +6 -6
- package/core/entity_id_parse.mjs +7 -7
- package/core/hexIds.mjs +6 -18
- package/crypto/checkpoint_sign.mjs +4 -4
- package/dag/canonicalize_row.mjs +4 -7
- package/discovery/adverts.mjs +1 -2
- package/discovery/bt/index.mjs +11 -11
- package/discovery/bt/peer_hints.mjs +4 -7
- package/discovery/index.mjs +8 -12
- package/discovery/internal/signal_crypto.mjs +1 -2
- package/discovery/lan.mjs +7 -9
- package/discovery/lan_peer_hints.mjs +5 -8
- package/discovery/nostr.mjs +19 -20
- package/discovery/peer_clue.mjs +1 -4
- package/federation/entity_key_chain.mjs +10 -9
- package/federation/message_rate_limit.mjs +1 -3
- package/files/manifest/normalize.mjs +2 -2
- package/files/manifest/public.mjs +5 -5
- package/governance/branch.mjs +1 -2
- package/governance/join_pow.mjs +2 -4
- package/governance/owner_succession_ballot.mjs +5 -5
- package/infra/priority.mjs +1 -2
- package/link/handshake.mjs +21 -21
- package/link/pipe.mjs +1 -2
- package/link/providers/ble_gatt.mjs +2 -3
- package/link/providers/lan_tcp.mjs +2 -3
- package/link/providers/link_id_pipe.mjs +3 -3
- package/link/providers/nostr.mjs +6 -6
- package/mailbox/deliver_or_store.mjs +11 -11
- package/mailbox/prune.mjs +3 -4
- package/mailbox/rate.mjs +3 -2
- package/mailbox/store.mjs +6 -9
- package/node/denylist.mjs +25 -28
- package/node/identity.mjs +6 -6
- package/node/network.mjs +12 -12
- package/node/personal_block.mjs +4 -4
- package/node/reputation_store.mjs +13 -13
- package/node/reputation_sync.mjs +9 -10
- package/package.json +1 -1
- package/reputation/engine.mjs +14 -16
- package/schemas/discovery.mjs +5 -6
- package/schemas/federation_pull.mjs +7 -7
- package/schemas/mailbox.mjs +3 -3
- package/schemas/part_query.mjs +5 -5
- package/timeline/verify_remote.mjs +7 -7
- package/transport/link_registry.mjs +16 -19
- package/transport/mesh_keepalive.mjs +8 -11
- package/transport/offer_answer.mjs +3 -3
- package/transport/peer_health.mjs +12 -15
- package/trust_graph/send.mjs +3 -3
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { Buffer } from 'node:buffer'
|
|
5
5
|
|
|
6
|
-
import { isHex64, isSignatureHex128
|
|
6
|
+
import { isHex64, isSignatureHex128 } 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,12 +12,12 @@ import { eventBodyForSign, signPayloadBytes } from '../dag/index.mjs'
|
|
|
12
12
|
* @returns {Promise<boolean>} 验签是否通过
|
|
13
13
|
*/
|
|
14
14
|
export async function verifyTimelineRemoteSignature(event) {
|
|
15
|
-
const sender =
|
|
16
|
-
if (!
|
|
17
|
-
const signatureHex =
|
|
18
|
-
if (!
|
|
19
|
-
const pubKeyHex =
|
|
20
|
-
if (!
|
|
15
|
+
const sender = isHex64(event?.sender)
|
|
16
|
+
if (!sender) return false
|
|
17
|
+
const signatureHex = isSignatureHex128(event?.signature)
|
|
18
|
+
if (!signatureHex) return false
|
|
19
|
+
const pubKeyHex = isHex64(event?.senderPubKey)
|
|
20
|
+
if (!pubKeyHex) return false
|
|
21
21
|
const publicKeyBytes = new Uint8Array(Buffer.from(pubKeyHex, 'hex'))
|
|
22
22
|
if (pubKeyHash(publicKeyBytes) !== sender) return false
|
|
23
23
|
const signatureBytes = new Uint8Array(Buffer.from(signatureHex, 'hex'))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer'
|
|
2
2
|
|
|
3
|
-
import { compareHex64Asc,
|
|
3
|
+
import { compareHex64Asc, isHex64 } from '../core/hexIds.mjs'
|
|
4
4
|
import { keyPairFromSeed } from '../crypto/crypto.mjs'
|
|
5
5
|
import { noteAdvertPeerHints } from '../discovery/advert_peer_hints.mjs'
|
|
6
6
|
import { watchVerifiedNodeAdvert, setDiscoveryLinkDialer, prepareConnectToNode } from '../discovery/index.mjs'
|
|
@@ -34,8 +34,8 @@ const DIAL_COOLDOWN_MAX_MS = ms('10m')
|
|
|
34
34
|
function resolveLocalIdentity(localIdentity) {
|
|
35
35
|
if (localIdentity?.nodeHash && localIdentity?.nodePubKey && localIdentity?.secretKey)
|
|
36
36
|
return {
|
|
37
|
-
nodeHash:
|
|
38
|
-
nodePubKey:
|
|
37
|
+
nodeHash: localIdentity.nodeHash,
|
|
38
|
+
nodePubKey: localIdentity.nodePubKey,
|
|
39
39
|
secretKey: localIdentity.secretKey,
|
|
40
40
|
}
|
|
41
41
|
const secretKey = Buffer.from(ensureNodeSeed(), 'hex')
|
|
@@ -154,7 +154,7 @@ export function createLinkRegistry(options = {}) {
|
|
|
154
154
|
* @returns {Promise<void>}
|
|
155
155
|
*/
|
|
156
156
|
async function registerResolvedLink(remoteNodeHash, candidate) {
|
|
157
|
-
const normalized =
|
|
157
|
+
const normalized = remoteNodeHash
|
|
158
158
|
const existing = links.get(normalized)
|
|
159
159
|
if (existing && existing !== candidate && !linkIsPreferred(candidate, normalized, existing)) {
|
|
160
160
|
await candidate.close(candidate.level !== existing.level ? 'provider-loser' : 'glare-loser')
|
|
@@ -176,7 +176,7 @@ export function createLinkRegistry(options = {}) {
|
|
|
176
176
|
void (async () => {
|
|
177
177
|
try {
|
|
178
178
|
await link.ready
|
|
179
|
-
const remote =
|
|
179
|
+
const remote = isHex64(link.nodeHash)
|
|
180
180
|
if (!remote) {
|
|
181
181
|
await link.close('inbound-no-nodehash')
|
|
182
182
|
return
|
|
@@ -241,7 +241,7 @@ export function createLinkRegistry(options = {}) {
|
|
|
241
241
|
* @param {string} remoteNodeHash 远端 nodeHash
|
|
242
242
|
* @returns {object | null} 已有规范链路
|
|
243
243
|
*/
|
|
244
|
-
getCanonicalLink: remoteNodeHash => links.get(
|
|
244
|
+
getCanonicalLink: remoteNodeHash => links.get(remoteNodeHash),
|
|
245
245
|
}))
|
|
246
246
|
|
|
247
247
|
/**
|
|
@@ -271,7 +271,7 @@ export function createLinkRegistry(options = {}) {
|
|
|
271
271
|
async function ensureDirectLinkToNode(remoteNodeHash) {
|
|
272
272
|
await bootstrap.ensureRuntime()
|
|
273
273
|
await bootstrap.whenSignalListening()
|
|
274
|
-
const normalized =
|
|
274
|
+
const normalized = remoteNodeHash
|
|
275
275
|
if (!normalized || normalized === localIdentity.nodeHash) return null
|
|
276
276
|
if (links.has(normalized)) return links.get(normalized)
|
|
277
277
|
if (inflights.has(normalized)) return await inflights.get(normalized)
|
|
@@ -356,7 +356,7 @@ export function createLinkRegistry(options = {}) {
|
|
|
356
356
|
* @param {string} nodeHash 目标 nodeHash
|
|
357
357
|
* @returns {object | null} 已有链路或 null
|
|
358
358
|
*/
|
|
359
|
-
getLink: nodeHash => links.get(
|
|
359
|
+
getLink: nodeHash => links.get(nodeHash) || null,
|
|
360
360
|
ensureLinkToNode: ensureDirectLinkToNode,
|
|
361
361
|
/**
|
|
362
362
|
* @param {(nodeHash: string) => void} listener link up 回调
|
|
@@ -380,10 +380,9 @@ export function createLinkRegistry(options = {}) {
|
|
|
380
380
|
exploreLinkHashes = meshKeepalive.exploreLinkHashes
|
|
381
381
|
setDiscoveryLinkDialer(ensureDirectLinkToNode)
|
|
382
382
|
setDiscoveryPeerClueListener(nodeHash => {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
recentAdverts.touch(hash, Date.now())
|
|
383
|
+
if (!nodeHash) return
|
|
384
|
+
dialCooldown.delete(nodeHash)
|
|
385
|
+
recentAdverts.touch(nodeHash, Date.now())
|
|
387
386
|
})
|
|
388
387
|
|
|
389
388
|
/**
|
|
@@ -401,7 +400,7 @@ export function createLinkRegistry(options = {}) {
|
|
|
401
400
|
* @returns {Promise<boolean>} 是否发送成功
|
|
402
401
|
*/
|
|
403
402
|
async function sendDirectToNodeLink(remoteNodeHash, envelope) {
|
|
404
|
-
const normalized =
|
|
403
|
+
const normalized = remoteNodeHash
|
|
405
404
|
if (!normalized) return false
|
|
406
405
|
const link = links.get(normalized)
|
|
407
406
|
if (!link) return false
|
|
@@ -591,7 +590,7 @@ export function createLinkRegistry(options = {}) {
|
|
|
591
590
|
* @returns {object | null} 链路实例;不存在时 null
|
|
592
591
|
*/
|
|
593
592
|
getLink(nodeHash) {
|
|
594
|
-
return links.get(
|
|
593
|
+
return links.get(nodeHash) || null
|
|
595
594
|
},
|
|
596
595
|
/**
|
|
597
596
|
* 列出所有活跃链路。
|
|
@@ -607,8 +606,7 @@ export function createLinkRegistry(options = {}) {
|
|
|
607
606
|
* @returns {Promise<void>}
|
|
608
607
|
*/
|
|
609
608
|
async closeLink(nodeHash, reason = 'manual-close') {
|
|
610
|
-
const
|
|
611
|
-
const link = links.get(normalized)
|
|
609
|
+
const link = links.get(nodeHash)
|
|
612
610
|
if (!link) return
|
|
613
611
|
await link.close(reason)
|
|
614
612
|
},
|
|
@@ -638,7 +636,7 @@ export function createLinkRegistry(options = {}) {
|
|
|
638
636
|
* @returns {void}
|
|
639
637
|
*/
|
|
640
638
|
registerScopeInterest(scope, nodeHashes) {
|
|
641
|
-
scopeInterests.set(scope, new Set((nodeHashes || []).
|
|
639
|
+
scopeInterests.set(scope, new Set((nodeHashes || []).filter(isHex64)))
|
|
642
640
|
},
|
|
643
641
|
/**
|
|
644
642
|
* 释放 scope 兴趣。
|
|
@@ -673,8 +671,7 @@ export function createLinkRegistry(options = {}) {
|
|
|
673
671
|
* @returns {Promise<() => void>} 取消函数
|
|
674
672
|
*/
|
|
675
673
|
async watchNodeAdvert(nodeHash, onAdvert) {
|
|
676
|
-
|
|
677
|
-
return await watchVerifiedNodeAdvert(hash, async (verifiedNodeHash, body, meta) => {
|
|
674
|
+
return await watchVerifiedNodeAdvert(nodeHash, async (verifiedNodeHash, body, meta) => {
|
|
678
675
|
noteAdvertPeerHints(verifiedNodeHash, body, meta)
|
|
679
676
|
recentAdverts.touch(verifiedNodeHash, Date.now())
|
|
680
677
|
dialCooldown.delete(verifiedNodeHash)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { normalizeHex64 } from '../core/hexIds.mjs'
|
|
2
1
|
import { listVisibleNodeHashes as discoveryListVisible } from '../discovery/index.mjs'
|
|
3
2
|
import { nodeDebug, shortHash } from '../node/log.mjs'
|
|
4
3
|
import { applyNetworkHint, loadPeerPoolView, promoteExplorePeer } from '../node/network.mjs'
|
|
@@ -54,15 +53,14 @@ export function createMeshKeepalive(deps) {
|
|
|
54
53
|
* @returns {void}
|
|
55
54
|
*/
|
|
56
55
|
function syncExploreMark(nodeHash, trustedPeers) {
|
|
57
|
-
|
|
58
|
-
if (!hash) return
|
|
56
|
+
if (!nodeHash) return
|
|
59
57
|
const trusted = trustedPeers ?? loadPeerPoolView().trustedPeers
|
|
60
|
-
if (trusted.includes(
|
|
61
|
-
exploreLinks.delete(
|
|
62
|
-
exploreStableSince.delete(
|
|
58
|
+
if (trusted.includes(nodeHash)) {
|
|
59
|
+
exploreLinks.delete(nodeHash)
|
|
60
|
+
exploreStableSince.delete(nodeHash)
|
|
63
61
|
return
|
|
64
62
|
}
|
|
65
|
-
exploreLinks.add(
|
|
63
|
+
exploreLinks.add(nodeHash)
|
|
66
64
|
}
|
|
67
65
|
|
|
68
66
|
/**
|
|
@@ -187,10 +185,9 @@ export function createMeshKeepalive(deps) {
|
|
|
187
185
|
syncExploreMark(nodeHash)
|
|
188
186
|
}) ?? null
|
|
189
187
|
stopLinkDown = registry.onLinkDown((nodeHash, reason) => {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
nodeDebug('p2p:mesh link down', { peer: shortHash(hash), reason })
|
|
188
|
+
exploreLinks.delete(nodeHash)
|
|
189
|
+
exploreStableSince.delete(nodeHash)
|
|
190
|
+
nodeDebug('p2p:mesh link down', { peer: shortHash(nodeHash), reason })
|
|
194
191
|
// 主动关链不立刻补洞(避免 budget-evict 刚踢又连);意外断链用 tick 按 N/K 重选,而非粘住原对端。
|
|
195
192
|
if (isMeshIntentionalClose(reason)) return
|
|
196
193
|
void runTick()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { isHex64 } from '../core/hexIds.mjs'
|
|
4
4
|
import { decryptNodeSignalPacket, sendNodeSignalPacket } from '../discovery/index.mjs'
|
|
5
5
|
import { listLinkProviders } from '../link/providers/index.mjs'
|
|
6
6
|
import { nodeDebug, shortHash } from '../node/log.mjs'
|
|
@@ -92,7 +92,7 @@ export function createOfferAnswerDial(deps) {
|
|
|
92
92
|
* @returns {ReturnType<typeof createBufferedSignalSession>} 该连接的缓冲信令会话
|
|
93
93
|
*/
|
|
94
94
|
function createConnSession(remoteNodeHash, connId) {
|
|
95
|
-
const normalized =
|
|
95
|
+
const normalized = remoteNodeHash
|
|
96
96
|
return createBufferedSignalSession(async message => {
|
|
97
97
|
await sendNodeSignalPacket(normalized, {
|
|
98
98
|
type: 'signal',
|
|
@@ -153,7 +153,7 @@ export function createOfferAnswerDial(deps) {
|
|
|
153
153
|
*/
|
|
154
154
|
async function handleSignalPacket(packet) {
|
|
155
155
|
if (packet?.type !== 'signal') return
|
|
156
|
-
const remoteNodeHash =
|
|
156
|
+
const remoteNodeHash = isHex64(packet.from)
|
|
157
157
|
const connId = String(packet.connId || '')
|
|
158
158
|
if (!remoteNodeHash || remoteNodeHash === localIdentity.nodeHash || !connId) return
|
|
159
159
|
let session = signalSessions.get(connId)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { normalizeHex64 } from '../core/hexIds.mjs'
|
|
2
1
|
import { emitSafe } from '../utils/emit_safe.mjs'
|
|
3
2
|
|
|
4
3
|
/**
|
|
@@ -56,37 +55,35 @@ export function createPeerHealthTracker(registry) {
|
|
|
56
55
|
}
|
|
57
56
|
|
|
58
57
|
const stopUp = registry.onLinkUp?.((nodeHash, link) => {
|
|
59
|
-
|
|
60
|
-
if (!hash) return
|
|
58
|
+
if (!nodeHash) return
|
|
61
59
|
const stopRtt = link?.onRtt?.(() => {
|
|
62
60
|
const stats = link.stats?.() ?? {}
|
|
63
|
-
update(
|
|
61
|
+
update(nodeHash, {
|
|
64
62
|
rttMs: stats.rttMs ?? null,
|
|
65
63
|
avgRttMs: stats.avgRttMs ?? null,
|
|
66
64
|
lastSeenAt: Date.now(),
|
|
67
65
|
})
|
|
68
66
|
}) ?? null
|
|
69
67
|
const stopDown = link?.onDown?.(() => {
|
|
70
|
-
cleanups.get(
|
|
71
|
-
cleanups.delete(
|
|
72
|
-
update(
|
|
68
|
+
cleanups.get(nodeHash)?.()
|
|
69
|
+
cleanups.delete(nodeHash)
|
|
70
|
+
update(nodeHash, { connected: false, lastSeenAt: Date.now() })
|
|
73
71
|
}) ?? null
|
|
74
|
-
update(
|
|
72
|
+
update(nodeHash, {
|
|
75
73
|
connected: true,
|
|
76
74
|
source: link?.providerId ?? null,
|
|
77
75
|
lastSeenAt: Date.now(),
|
|
78
76
|
})
|
|
79
|
-
cleanups.set(
|
|
77
|
+
cleanups.set(nodeHash, () => {
|
|
80
78
|
stopRtt?.()
|
|
81
79
|
stopDown?.()
|
|
82
80
|
})
|
|
83
81
|
}) ?? null
|
|
84
82
|
const stopDown = registry.onLinkDown?.(nodeHash => {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
cleanups.
|
|
88
|
-
|
|
89
|
-
update(hash, { connected: false, lastSeenAt: Date.now() })
|
|
83
|
+
if (!nodeHash) return
|
|
84
|
+
cleanups.get(nodeHash)?.()
|
|
85
|
+
cleanups.delete(nodeHash)
|
|
86
|
+
update(nodeHash, { connected: false, lastSeenAt: Date.now() })
|
|
90
87
|
}) ?? null
|
|
91
88
|
|
|
92
89
|
return {
|
|
@@ -95,7 +92,7 @@ export function createPeerHealthTracker(registry) {
|
|
|
95
92
|
* @returns {PeerHealthEntry | null} 健康记录;无记录时 null
|
|
96
93
|
*/
|
|
97
94
|
getPeerHealth(nodeHash) {
|
|
98
|
-
return entries.get(
|
|
95
|
+
return entries.get(nodeHash) ?? null
|
|
99
96
|
},
|
|
100
97
|
/**
|
|
101
98
|
* @returns {PeerHealthEntry[]} 所有邻居健康记录
|
package/trust_graph/send.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isHex64
|
|
1
|
+
import { isHex64 } from '../core/hexIds.mjs'
|
|
2
2
|
import { getNodeHash } from '../node/identity.mjs'
|
|
3
3
|
import { loadReputation } from '../node/reputation_store.mjs'
|
|
4
4
|
import { listFederationRoomSlots } from '../registries/room_provider.mjs'
|
|
@@ -20,8 +20,8 @@ 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 =
|
|
24
|
-
if (!
|
|
23
|
+
const target = isHex64(targetNodeHash)
|
|
24
|
+
if (!target) return false
|
|
25
25
|
|
|
26
26
|
// 已直连 peer 不经 trust-graph scope 也应能收发 node scope action(非成员 CAS chunk / follow hint 等)
|
|
27
27
|
if (await sendToNodeLink(target, { scope: 'node', action: actionName, payload }))
|