@steve02081504/fount-p2p 0.0.22 → 0.0.24

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.
Files changed (71) hide show
  1. package/README.md +8 -6
  2. package/core/entity_id.mjs +5 -5
  3. package/core/logical_entity.mjs +1 -1
  4. package/core/object.mjs +9 -0
  5. package/core/partpath.mjs +10 -0
  6. package/core/random_id.mjs +4 -2
  7. package/crypto/crypto.mjs +26 -93
  8. package/dag/storage.mjs +0 -5
  9. package/dag/strip_extensions.mjs +1 -3
  10. package/{wire/part_query_cache.mjs → federation/part_query/cache.mjs} +7 -7
  11. package/{wire/part_query.mjs → federation/part_query/runtime.mjs} +36 -77
  12. package/files/assemble.mjs +5 -5
  13. package/files/assemble_stream.mjs +2 -2
  14. package/files/chunk/fetch.mjs +101 -0
  15. package/files/chunk/pending.mjs +59 -0
  16. package/files/{chunk_provider_registry.mjs → chunk/provider_registry.mjs} +4 -5
  17. package/files/chunk/responder.mjs +104 -0
  18. package/files/{chunk_store.mjs → chunk/store.mjs} +2 -2
  19. package/files/{chunk_fetch_verify.mjs → chunk/verify.mjs} +2 -2
  20. package/files/evfs.mjs +18 -19
  21. package/files/fed/fetch_shared.mjs +39 -0
  22. package/files/fed/responder.mjs +47 -0
  23. package/files/{acl.mjs → manifest/acl.mjs} +4 -4
  24. package/files/{manifest_acl_registry.mjs → manifest/acl_registry.mjs} +14 -17
  25. package/files/{manifest_fetch.mjs → manifest/fetch.mjs} +47 -52
  26. package/files/{manifest.mjs → manifest/normalize.mjs} +2 -2
  27. package/files/manifest/pending.mjs +53 -0
  28. package/files/{public_manifest.mjs → manifest/public.mjs} +12 -12
  29. package/files/transfer_key.mjs +2 -2
  30. package/files/transfer_key_registry.mjs +7 -7
  31. package/index.mjs +4 -4
  32. package/infra/service.mjs +1 -1
  33. package/link/handshake.mjs +0 -3
  34. package/mailbox/deliver_or_store.mjs +0 -2
  35. package/mailbox/parse.mjs +1 -1
  36. package/mailbox/store.mjs +0 -9
  37. package/mailbox/wire.mjs +24 -16
  38. package/node/reputation_sync.mjs +33 -29
  39. package/package.json +9 -8
  40. package/registries/inbound.mjs +19 -9
  41. package/registries/part_path.mjs +3 -8
  42. package/schemas/discovery.mjs +1 -1
  43. package/schemas/federation_pull.mjs +1 -1
  44. package/schemas/part_query.mjs +6 -5
  45. package/transport/group_link_set.mjs +2 -2
  46. package/transport/link_registry.mjs +2 -2
  47. package/transport/node_scope/features.mjs +166 -0
  48. package/transport/node_scope/wire.mjs +124 -0
  49. package/transport/remote_user_room.mjs +0 -1
  50. package/{rooms → transport}/scoped_link.mjs +1 -1
  51. package/transport/user_room.mjs +15 -28
  52. package/utils/fetch_wait.mjs +98 -0
  53. package/wire/adapter.mjs +13 -0
  54. package/wire/ingress.mjs +1 -8
  55. package/wire/part/fanout.mjs +99 -0
  56. package/wire/part/group.mjs +45 -0
  57. package/wire/{part_ingress.mjs → part/ingress.mjs} +58 -67
  58. package/wire/{part_invoke.mjs → part/invoke.mjs} +1 -22
  59. package/wire/part/pending.mjs +32 -0
  60. package/wire/part/query.mjs +91 -0
  61. package/wire/subscribe.mjs +13 -0
  62. package/federation/chunk_fetch_pending.mjs +0 -124
  63. package/federation/manifest_fetch_pending.mjs +0 -88
  64. package/files/chunk_fetch.mjs +0 -99
  65. package/files/chunk_responder.mjs +0 -142
  66. package/transport/advert_ingest.mjs +0 -17
  67. package/transport/node_scope.mjs +0 -289
  68. package/wire/group_part.mjs +0 -43
  69. package/wire/part_common.mjs +0 -55
  70. package/wire/part_fanout.mjs +0 -52
  71. /package/{wire → schemas}/part_query.tunables.json +0 -0
@@ -1,124 +0,0 @@
1
- import { base64ToBytes } from '../core/bytes_codec.mjs'
2
- import { verifiedChunkBytes } from '../files/chunk_fetch_verify.mjs'
3
-
4
- /** @type {Map<string, { expectedHash: string, timer: ReturnType<typeof setTimeout>, resolve: (v: Uint8Array | null) => void, reject?: (e: Error) => void }>} */
5
- export const pendingChunkFetches = new Map()
6
-
7
- /** 并发 pending chunk fetch 上限。 */
8
- export const MAX_PENDING_CHUNK_FETCHES = 2048
9
-
10
- /**
11
- * 注册 chunk 拉取等待槽(requestId 或 compositeKey)。
12
- * @param {string} key 唯一等待键
13
- * @param {string} expectedHash 期望 64 hex 密文哈希
14
- * @param {number} timeoutMs 超时毫秒
15
- * @param {{ rejectOnTimeout?: boolean }} [options] rejectOnTimeout 时 Promise 以 Error 拒绝
16
- * @returns {{ done: Promise<Uint8Array | null>, cancel: () => void }} 等待 Promise 与取消函数
17
- */
18
- export function registerChunkFetchWait(key, expectedHash, timeoutMs, options = {}) {
19
- if (!key || pendingChunkFetches.size >= MAX_PENDING_CHUNK_FETCHES)
20
- return {
21
- done: Promise.resolve(null),
22
- /**
23
- * 槽位已满时的空操作取消。
24
- */
25
- cancel: () => { },
26
- }
27
-
28
- /** @type {(value: Uint8Array | null | Error) => void} */
29
- let settle
30
- const done = new Promise((resolve, reject) => {
31
- /**
32
- * @param {Uint8Array | null | Error} value 块数据或错误
33
- * @returns {void}
34
- */
35
- function settleWait(value) {
36
- if (value instanceof Error) reject(value)
37
- else resolve(value)
38
- }
39
- settle = settleWait
40
- })
41
- const timer = setTimeout(() => {
42
- pendingChunkFetches.delete(key)
43
- if (options.rejectOnTimeout)
44
- settle(new Error('chunk fetch timeout'))
45
- else
46
- settle(null)
47
- }, timeoutMs)
48
-
49
- /**
50
- * @param {Uint8Array | null} data 块数据
51
- * @returns {void}
52
- */
53
- function finish(data) {
54
- clearTimeout(timer)
55
- pendingChunkFetches.delete(key)
56
- settle(data)
57
- }
58
-
59
- /**
60
- * @param {Error | unknown} error 拒绝原因
61
- * @returns {void}
62
- */
63
- function rejectWait(error) {
64
- finish(error instanceof Error ? error : new Error(String(error)))
65
- }
66
-
67
- pendingChunkFetches.set(key, {
68
- expectedHash,
69
- timer,
70
- resolve: finish,
71
- reject: rejectWait,
72
- })
73
-
74
- return {
75
- done,
76
- /** 取消等待并以 null settle。 */
77
- cancel: () => {
78
- clearTimeout(timer)
79
- pendingChunkFetches.delete(key)
80
- settle(null)
81
- },
82
- }
83
- }
84
-
85
- /**
86
- * 按等待键解析入站 chunk 响应(校验哈希后 resolve)。
87
- * @param {string} key 等待键
88
- * @param {string} expectedHash 期望哈希
89
- * @param {Uint8Array | null} bytes 密文块
90
- * @returns {boolean} 是否命中并完成等待
91
- */
92
- export function resolveChunkFetchWait(key, expectedHash, bytes) {
93
- const entry = pendingChunkFetches.get(key)
94
- if (!entry || entry.expectedHash !== expectedHash) return false
95
- const verified = bytes ? verifiedChunkBytes(expectedHash, bytes) : null
96
- if (bytes && !verified) return false
97
- clearTimeout(entry.timer)
98
- pendingChunkFetches.delete(key)
99
- entry.resolve(verified)
100
- return true
101
- }
102
-
103
- /**
104
- * 处理 fed_chunk_data / 带 requestId 的响应载荷。
105
- * @param {object} payload 入站载荷
106
- * @returns {boolean} 是否命中 pending
107
- */
108
- export function resolvePendingChunkFetch(payload) {
109
- const requestId = typeof payload?.requestId === 'string' ? payload.requestId : ''
110
- if (!requestId) return false
111
- const entry = pendingChunkFetches.get(requestId)
112
- if (!entry) return false
113
- if (typeof payload?.dataBase64 === 'string') {
114
- try {
115
- return resolveChunkFetchWait(requestId, entry.expectedHash, base64ToBytes(payload.dataBase64))
116
- }
117
- catch { /* keep waiting */ }
118
- return false
119
- }
120
- clearTimeout(entry.timer)
121
- pendingChunkFetches.delete(requestId)
122
- entry.resolve(null)
123
- return true
124
- }
@@ -1,88 +0,0 @@
1
- import { verifySignedPublicManifest } from '../files/public_manifest.mjs'
2
-
3
- /** @type {Map<string, { expectedKey: string, timer: ReturnType<typeof setTimeout>, resolve: (v: object | null) => void }>} */
4
- export const pendingManifestFetches = new Map()
5
-
6
- /** 并发 pending manifest fetch 上限。 */
7
- export const MAX_PENDING_MANIFEST_FETCHES = 512
8
-
9
- /**
10
- * @param {string} ownerEntityHash owner
11
- * @param {string} logicalPath 路径
12
- * @returns {string} 期望键
13
- */
14
- export function manifestFetchExpectedKey(ownerEntityHash, logicalPath) {
15
- return `${String(ownerEntityHash || '').trim().toLowerCase()}\0${String(logicalPath || '').trim().replace(/^\/+/, '')}`
16
- }
17
-
18
- /**
19
- * @param {string} key requestId
20
- * @param {string} expectedKey owner+path 复合键
21
- * @param {number} timeoutMs 超时毫秒
22
- * @returns {{ done: Promise<object | null>, cancel: () => void }} 等待 Promise 与取消
23
- */
24
- export function registerManifestFetchWait(key, expectedKey, timeoutMs) {
25
- if (!key || pendingManifestFetches.size >= MAX_PENDING_MANIFEST_FETCHES)
26
- return {
27
- done: Promise.resolve(null),
28
- /**
29
- * 槽位已满时的空操作取消。
30
- */
31
- cancel: () => { },
32
- }
33
-
34
- /** @type {(value: object | null) => void} */
35
- let settle
36
- const done = new Promise(resolve => {
37
- settle = resolve
38
- })
39
- const timer = setTimeout(() => {
40
- pendingManifestFetches.delete(key)
41
- settle(null)
42
- }, timeoutMs)
43
-
44
- pendingManifestFetches.set(key, {
45
- expectedKey,
46
- timer,
47
- /**
48
- * @param {object | null} value 验签后的 manifest,超时/取消为 null
49
- */
50
- resolve: value => {
51
- clearTimeout(timer)
52
- pendingManifestFetches.delete(key)
53
- settle(value)
54
- },
55
- })
56
-
57
- return {
58
- done,
59
- /**
60
- * 取消等待并以 null 结算。
61
- */
62
- cancel: () => {
63
- clearTimeout(timer)
64
- pendingManifestFetches.delete(key)
65
- settle(null)
66
- },
67
- }
68
- }
69
-
70
- /**
71
- * 处理 fed_manifest_data:验签通过后 resolve pending。
72
- * @param {object} payload 入站载荷
73
- * @returns {Promise<boolean>} 是否命中并完成等待
74
- */
75
- export async function resolvePendingManifestFetch(payload) {
76
- const requestId = String(payload?.requestId || '')
77
- if (!requestId) return false
78
- const entry = pendingManifestFetches.get(requestId)
79
- if (!entry) return false
80
-
81
- const verified = await verifySignedPublicManifest(payload?.manifest)
82
- if (!verified) return false
83
- const key = manifestFetchExpectedKey(verified.ownerEntityHash, verified.logicalPath)
84
- if (key !== entry.expectedKey) return false
85
-
86
- entry.resolve(verified)
87
- return true
88
- }
@@ -1,99 +0,0 @@
1
- import { randomUUID } from 'node:crypto'
2
-
3
- import { bytesToBase64 } from '../core/bytes_codec.mjs'
4
- import {
5
- MAX_PENDING_CHUNK_FETCHES,
6
- registerChunkFetchWait,
7
- } from '../federation/chunk_fetch_pending.mjs'
8
- import { ms } from '../utils/duration.mjs'
9
- import { createInflightTable } from '../utils/inflight_table.mjs'
10
-
11
- import { verifiedChunkBytes } from './chunk_fetch_verify.mjs'
12
- import { fetchFederationChunk, resolveNodeHash } from './chunk_provider_registry.mjs'
13
- import { getChunk, hasChunk, putChunk } from './chunk_store.mjs'
14
- import { fanoutFedFetch } from './fetch_fanout.mjs'
15
-
16
- const DEFAULT_CHUNK_FETCH_TIMEOUT_MS = ms('8s')
17
-
18
- /** 同 username+hash 共享一次 fanout;队满只丢已超基础超时的队首。 */
19
- const chunkInflight = createInflightTable({
20
- maxSize: MAX_PENDING_CHUNK_FETCHES,
21
- baseTimeoutMs: DEFAULT_CHUNK_FETCH_TIMEOUT_MS,
22
- })
23
-
24
- /**
25
- * @typedef {{
26
- * username: string,
27
- * ciphertextHash: string,
28
- * ownerEntityHash?: string,
29
- * groupId?: string,
30
- * }} FetchChunkContext
31
- */
32
-
33
- /**
34
- * @param {FetchChunkContext} context 上下文
35
- * @returns {Promise<Uint8Array | null>} 密文块
36
- */
37
- export async function fetchChunk(context) {
38
- const hash = String(context.ciphertextHash || '').trim().toLowerCase()
39
- const { username } = context
40
- if (!hash || !username) return null
41
-
42
- if (await hasChunk(hash))
43
- return new Uint8Array(await getChunk(hash))
44
-
45
- if (context.groupId) {
46
- const u8 = await fetchFederationChunk(username, context.groupId, hash)
47
- const verified = verifiedChunkBytes(hash, u8)
48
- if (verified) {
49
- await putChunk(hash, verified)
50
- return verified
51
- }
52
- }
53
-
54
- const inflightKey = `${username}\0${hash}`
55
- const shared = chunkInflight.acquire(inflightKey, () => {
56
- const requestId = randomUUID()
57
- const wait = registerChunkFetchWait(requestId, hash, DEFAULT_CHUNK_FETCH_TIMEOUT_MS)
58
- void (async () => {
59
- try {
60
- const { nodeHash } = await resolveNodeHash(username)
61
- await fanoutFedFetch(username, 'fed_chunk_get', {
62
- requestId,
63
- nodeHash,
64
- chunkHash: hash,
65
- ownerEntityHash: context.ownerEntityHash,
66
- })
67
- }
68
- catch { /* pending wait 超时/cancel 负责 settle */ }
69
- })()
70
- return { done: wait.done, cancel: wait.cancel }
71
- })
72
- if (!shared) return null
73
-
74
- const result = await shared
75
- const verified = verifiedChunkBytes(hash, result)
76
- if (verified) {
77
- await putChunk(hash, verified)
78
- return verified
79
- }
80
-
81
- return null
82
- }
83
-
84
- /**
85
- * 若本机有 chunk 则响应 fed_chunk_get。
86
- * @param {string} username 用户
87
- * @param {object} payload 请求
88
- * @param {(response: object, peerId: string) => void} sendResponse 发送
89
- * @param {string} peerId 对端
90
- * @returns {Promise<void>}
91
- */
92
- export async function handleIncomingChunkGet(username, payload, sendResponse, peerId) {
93
- const hash = String(payload?.chunkHash || '').trim().toLowerCase()
94
- if (!hash) return
95
- if (!await hasChunk(hash)) return
96
- const chunkBytes = await getChunk(hash)
97
- if (!chunkBytes?.length) return
98
- sendResponse({ requestId: payload.requestId, dataBase64: bytesToBase64(chunkBytes) }, peerId)
99
- }
@@ -1,142 +0,0 @@
1
- import { resolvePendingChunkFetch } from '../federation/chunk_fetch_pending.mjs'
2
- import { resolvePendingManifestFetch } from '../federation/manifest_fetch_pending.mjs'
3
-
4
- import { handleIncomingChunkGet } from './chunk_fetch.mjs'
5
- import { handleIncomingManifestGet } from './manifest_fetch.mjs'
6
-
7
- /**
8
- * @param {string} username 副本用户名 用户名
9
- * @param {object} data 入站 fed_chunk_get
10
- * @param {string} peerId 对端 id
11
- * @param {(resp: object, peerId: string) => void} sendChunkData 发送 fed_chunk_data
12
- * @returns {Promise<void>}
13
- */
14
- export async function handleFedChunkGetIngress(username, data, peerId, sendChunkData) {
15
- await handleIncomingChunkGet(username, data, sendChunkData, peerId)
16
- }
17
-
18
- /**
19
- * @param {object} data 入站 fed_chunk_data(含 requestId 时 resolve pending fetch)
20
- * @returns {void}
21
- */
22
- export function handleFedChunkDataIngress(data) {
23
- resolvePendingChunkFetch(data)
24
- }
25
-
26
- /**
27
- * @param {string} username 副本用户名
28
- * @param {object} data 入站 fed_manifest_get
29
- * @param {string} peerId 对端 id
30
- * @param {(resp: object, peerId: string) => void} sendManifestData 发送 fed_manifest_data
31
- * @returns {Promise<void>}
32
- */
33
- export async function handleFedManifestGetIngress(username, data, peerId, sendManifestData) {
34
- await handleIncomingManifestGet(username, data, sendManifestData, peerId)
35
- }
36
-
37
- /**
38
- * @param {object} data 入站 fed_manifest_data
39
- * @returns {Promise<void>}
40
- */
41
- export async function handleFedManifestDataIngress(data) {
42
- await resolvePendingManifestFetch(data)
43
- }
44
-
45
- /**
46
- * node scope user-room wire:注册 fed_chunk_* + fed_manifest_*。
47
- * @param {string | (() => string)} usernameOrGetter 副本用户名或 live getter
48
- * @param {{ on: (name: string, handler: (payload: unknown, peerId: string) => void) => (() => void) | void, send: (name: string, payload: unknown, peerId: string | null) => void }} wire action 表
49
- * @returns {() => void} 取消挂载的 dispose
50
- */
51
- export function attachNodeScopeFedChunkResponder(usernameOrGetter, wire) {
52
- const resolveUsername = typeof usernameOrGetter === 'function'
53
- ? usernameOrGetter
54
- : () => usernameOrGetter
55
- const offs = [
56
- wire.on('fed_chunk_get', (data, peerId) => {
57
- void handleFedChunkGetIngress(resolveUsername(), data, peerId, (resp, pid) => {
58
- try { wire.send('fed_chunk_data', resp, pid) }
59
- catch { /* disconnected */ }
60
- })
61
- }),
62
- wire.on('fed_chunk_data', handleFedChunkDataIngress),
63
- wire.on('fed_manifest_get', (data, peerId) => {
64
- void handleFedManifestGetIngress(resolveUsername(), data, peerId, (resp, pid) => {
65
- try { wire.send('fed_manifest_data', resp, pid) }
66
- catch { /* disconnected */ }
67
- })
68
- }),
69
- wire.on('fed_manifest_data', data => {
70
- void handleFedManifestDataIngress(data)
71
- }),
72
- ]
73
- return () => {
74
- for (const off of offs)
75
- try { off?.() } catch { /* ignore */ }
76
- }
77
- }
78
-
79
- /**
80
- * Trystero room:注册带 requestId 的 fed_chunk_* + fed_manifest_*(TrustGraph 全局 miss)。
81
- * @param {string} username 用户
82
- * @param {object} room Trystero room
83
- * @param {{ enqueue: (prio: number, cleanup: () => void) => void }} [fedOut] 出站队列
84
- * @param {(roomKey: string, action: string, rtcLimits: object) => boolean} [guardGet] RTC 负载守卫
85
- * @param {object} [rtcLimits] RTC 限额
86
- * @param {string} [roomKey] 房间键
87
- * @returns {void}
88
- */
89
- export function attachTrustGraphFedChunkResponder(username, room, fedOut, guardGet, rtcLimits = {}, roomKey = '') {
90
- const [sendChunkData, getChunkData] = room.makeAction('fed_chunk_data')
91
- const [, getChunkGet] = room.makeAction('fed_chunk_get')
92
- const [sendManifestData, getManifestData] = room.makeAction('fed_manifest_data')
93
- const [, getManifestGet] = room.makeAction('fed_manifest_get')
94
-
95
- getChunkGet((data, peerId) => {
96
- if (guardGet && !guardGet(roomKey, 'fed_chunk_get', rtcLimits)) return
97
- void (async () => {
98
- if (!data || typeof data !== 'object') return
99
- if (!String(data.requestId || '')) return
100
- await handleFedChunkGetIngress(username, data, peerId, (resp, pid) => {
101
- /**
102
- * 发送 fed_chunk_data 响应(可经 fedOut 限速队列)。
103
- */
104
- const send = () => {
105
- try { sendChunkData(resp, pid) }
106
- catch { /* peer gone — normal */ }
107
- }
108
- if (fedOut) fedOut.enqueue(6, send)
109
- else send()
110
- })
111
- })().catch(() => { /* peer/handler race — normal degrade */ })
112
- })
113
-
114
- getChunkData(data => {
115
- if (!data || typeof data !== 'object' || !data.requestId) return
116
- handleFedChunkDataIngress(data)
117
- })
118
-
119
- getManifestGet((data, peerId) => {
120
- if (guardGet && !guardGet(roomKey, 'fed_manifest_get', rtcLimits)) return
121
- void (async () => {
122
- if (!data || typeof data !== 'object') return
123
- if (!String(data.requestId || '')) return
124
- await handleFedManifestGetIngress(username, data, peerId, (resp, pid) => {
125
- /**
126
- * 发送 fed_manifest_data 响应(可经 fedOut 限速队列)。
127
- */
128
- const send = () => {
129
- try { sendManifestData(resp, pid) }
130
- catch { /* peer gone — normal */ }
131
- }
132
- if (fedOut) fedOut.enqueue(6, send)
133
- else send()
134
- })
135
- })().catch(() => { /* peer/handler race — normal degrade */ })
136
- })
137
-
138
- getManifestData(data => {
139
- if (!data || typeof data !== 'object' || !data.requestId) return
140
- void handleFedManifestDataIngress(data)
141
- })
142
- }
@@ -1,17 +0,0 @@
1
- import { noteAdvertPeerHints } from '../discovery/advert_peer_hints.mjs'
2
-
3
- /**
4
- * 解密并验签 advert(`ingestEncryptedAdvert` 别名)。不写 peer hints;写入需 `applyAdvertPeerHints`。Untrusted ingress。
5
- */
6
- export { ingestEncryptedAdvert as ingestSignedAdvert } from '../discovery/adverts.mjs'
7
-
8
- /**
9
- * 将已验签 advert 的 peer hints 写入本地(apply 侧;与 `ingestSignedAdvert` 分离)。
10
- * @param {string} verifiedNodeHash 已验签 nodeHash
11
- * @param {object} body advert body
12
- * @param {object} [meta] 元数据
13
- * @returns {void}
14
- */
15
- export function applyAdvertPeerHints(verifiedNodeHash, body, meta) {
16
- noteAdvertPeerHints(verifiedNodeHash, body, meta)
17
- }