@steve02081504/fount-p2p 0.0.40 → 0.0.41
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 +2 -2
- package/docs/evfs.md +6 -0
- package/files/chunk/fetch.mjs +6 -1
- package/files/evfs.mjs +7 -4
- package/files/fed/fetch_shared.mjs +8 -4
- package/files/fetch_fanout.mjs +16 -3
- package/files/manifest/fetch.mjs +1 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -39,7 +39,7 @@ Deno / native / BT: [runtime.md](docs/runtime.md).
|
|
|
39
39
|
|
|
40
40
|
## Tests / tools
|
|
41
41
|
|
|
42
|
-
- `npm test` — pure + integration (Node; `--test-force-exit`)
|
|
42
|
+
- `npm test` — pure + integration + frontend (Node; `--test-force-exit`)
|
|
43
43
|
- `npm run test:live` — live link / LAN smoke
|
|
44
44
|
- `npm run test:fount` — cross-repo Deno bridge (`test/fount/`); see [runtime.md](docs/runtime.md)
|
|
45
45
|
- `npm run test:sim` — tunables co-evolution (dev-only; [sim/AGENTS.md](sim/AGENTS.md))
|
|
@@ -73,7 +73,7 @@ Deno / native / BT: [runtime.md](docs/runtime.md).
|
|
|
73
73
|
|
|
74
74
|
| File | Directory |
|
|
75
75
|
|---|---|
|
|
76
|
-
| `tunables.json` | `reputation/`, `trust_graph/`, `mailbox/`, `governance/`, `dag/` |
|
|
76
|
+
| `tunables.json` | `transport/`, `infra/`, `reputation/`, `trust_graph/`, `mailbox/`, `governance/`, `dag/` |
|
|
77
77
|
| `part_query.tunables.json` | `schemas/` |
|
|
78
78
|
|
|
79
79
|
Sim harness: `sim/tunables_bundle.mjs` (dev-only). See [sim/AGENTS.md](sim/AGENTS.md).
|
package/docs/evfs.md
CHANGED
|
@@ -40,6 +40,12 @@ Concurrency: `putChunk` / `putChunkFromStream` / `deleteChunk` / the GC delete p
|
|
|
40
40
|
|
|
41
41
|
Outer caller timeouts must **not** abort the in-flight work — background fill continues after the caller gives up.
|
|
42
42
|
|
|
43
|
+
## `fetchChunk` / public-file chunk reads
|
|
44
|
+
|
|
45
|
+
- `fetchChunk` accepts the same `fanoutTargets` as `fetchManifest`. With targets it fanouts `fed_chunk_get` **only** to that node set; without targets it falls back to node-scope fanout. Same `username`+chunk hash+mode (`targeted`/`public`) is deduped in-flight.
|
|
46
|
+
- `readPublicFile` / `readManifestPlaintext` forward `options.fanoutTargets` to both the manifest fetch and the chunk fetch, so a cross-node public read can pull profile/avatar content straight from the owner node instead of depending on the node-scope fanout.
|
|
47
|
+
- The public (non-targeted) fanout does **not** block the request window on dialing the whole peer pool: already-linked / group-room-reachable peers are sent immediately, and unreachable peers are dialed in the background and re-sent once linked. (`ensureLinkToNode` no longer gates the send.)
|
|
48
|
+
|
|
43
49
|
## Non-public (ACL-gated) manifests
|
|
44
50
|
|
|
45
51
|
- `file-master-key-wrap` / `vault-wrap` / `identity-wrap` manifests carry no author signature, so the remote-acquisition trust boundary is **not** a signature — it is the **fanout target set** plus the **serving node's authorization**.
|
package/files/chunk/fetch.mjs
CHANGED
|
@@ -26,10 +26,14 @@ const chunkInflight = createInflightTable({
|
|
|
26
26
|
* ciphertextHash: string,
|
|
27
27
|
* ownerEntityHash?: string,
|
|
28
28
|
* groupId?: string,
|
|
29
|
+
* fanoutTargets?: string[],
|
|
29
30
|
* }} FetchChunkContext
|
|
30
31
|
*/
|
|
31
32
|
|
|
32
33
|
/**
|
|
34
|
+
* 拉取密文块。默认走 node-scope public 语义;
|
|
35
|
+
* 传入 `fanoutTargets` 时只向目标集 fanout(public 文件定向拉取,如 profile/avatar 直接向 owner 节点取块)。
|
|
36
|
+
* 同 username+hash+模式(含规范化目标集)in-flight 去重;本地已缓存块直接返回。
|
|
33
37
|
* @param {FetchChunkContext} context 上下文
|
|
34
38
|
* @returns {Promise<Uint8Array | null>} 密文块
|
|
35
39
|
*/
|
|
@@ -50,7 +54,7 @@ export async function fetchChunk(context) {
|
|
|
50
54
|
|
|
51
55
|
const shared = beginFedFanoutFetch({
|
|
52
56
|
inflight: chunkInflight,
|
|
53
|
-
|
|
57
|
+
inflightKeyBase: `${username}\0${hash}`,
|
|
54
58
|
username,
|
|
55
59
|
action: 'fed_chunk_get',
|
|
56
60
|
/**
|
|
@@ -69,6 +73,7 @@ export async function fetchChunk(context) {
|
|
|
69
73
|
chunkHash: hash,
|
|
70
74
|
ownerEntityHash: context.ownerEntityHash,
|
|
71
75
|
}),
|
|
76
|
+
fanoutTargets: context.fanoutTargets,
|
|
72
77
|
})
|
|
73
78
|
if (!shared) return null
|
|
74
79
|
|
package/files/evfs.mjs
CHANGED
|
@@ -32,7 +32,7 @@ function transferKeyDependenciesForReplica(replicaUsername, manifest) {
|
|
|
32
32
|
/**
|
|
33
33
|
* @param {string} username 拉取身份
|
|
34
34
|
* @param {import('./manifest/normalize.mjs').FileManifest} manifest 清单
|
|
35
|
-
* @param {{ fetchChunk?: Function }} [options] miss 拉取
|
|
35
|
+
* @param {{ fetchChunk?: Function, fanoutTargets?: string[] }} [options] miss 拉取
|
|
36
36
|
* @returns {Promise<boolean>} 全部 part 是否已就位
|
|
37
37
|
*/
|
|
38
38
|
async function ensureManifestPartsLocal(username, manifest, options = {}) {
|
|
@@ -43,6 +43,7 @@ async function ensureManifestPartsLocal(username, manifest, options = {}) {
|
|
|
43
43
|
ciphertextHash: part.hash,
|
|
44
44
|
ownerEntityHash: manifest.ownerEntityHash,
|
|
45
45
|
groupId: manifest.transferKeyDescriptor.groupId,
|
|
46
|
+
fanoutTargets: options.fanoutTargets,
|
|
46
47
|
})
|
|
47
48
|
if (!fetchedChunk) return false
|
|
48
49
|
await putChunk(part.hash, fetchedChunk)
|
|
@@ -94,7 +95,7 @@ export async function storeManifestParts(manifest, partBytes) {
|
|
|
94
95
|
/**
|
|
95
96
|
* @param {string} replicaUsername 副本用户名
|
|
96
97
|
* @param {import('./manifest/normalize.mjs').FileManifest} manifest 清单
|
|
97
|
-
* @param {{ username?: string, fetchChunk?: Function }} [options] miss 拉取
|
|
98
|
+
* @param {{ username?: string, fetchChunk?: Function, fanoutTargets?: string[] }} [options] miss 拉取
|
|
98
99
|
* @returns {Promise<Buffer | null>} 明文内容
|
|
99
100
|
*/
|
|
100
101
|
export async function readManifestPlaintext(replicaUsername, manifest, options = {}) {
|
|
@@ -118,7 +119,7 @@ export async function readManifestPlaintext(replicaUsername, manifest, options =
|
|
|
118
119
|
/**
|
|
119
120
|
* @param {string} replicaUsername 副本用户名
|
|
120
121
|
* @param {import('./manifest/normalize.mjs').FileManifest} manifest 清单
|
|
121
|
-
* @param {{ username?: string, fetchChunk?: Function }} [options] miss 拉取
|
|
122
|
+
* @param {{ username?: string, fetchChunk?: Function, fanoutTargets?: string[] }} [options] miss 拉取
|
|
122
123
|
* @returns {Promise<import('node:stream').Readable | null>} 明文流
|
|
123
124
|
*/
|
|
124
125
|
export async function readManifestPlaintextStream(replicaUsername, manifest, options = {}) {
|
|
@@ -229,10 +230,11 @@ export async function putFileManifestFromStream(parameters) {
|
|
|
229
230
|
|
|
230
231
|
/**
|
|
231
232
|
* 读取实体公开文件:本地 miss 时经网络取回签名 manifest,chunk miss 走既有 fetchChunk。
|
|
233
|
+
* 传入 `fanoutTargets` 时 manifest 与 chunk 拉取均定向到目标集(public 文件跨节点直取 owner 节点,避免 node-scope 大扇出)。
|
|
232
234
|
* @param {string} replicaUsername 副本用户名
|
|
233
235
|
* @param {string} entityHash owner entityHash
|
|
234
236
|
* @param {string} logicalPath EVFS 逻辑路径
|
|
235
|
-
* @param {{ username?: string, fetchChunk?: Function, revalidate?: boolean }} [options] miss 拉取;`revalidate` 强制阻塞等待 fanout 择新
|
|
237
|
+
* @param {{ username?: string, fetchChunk?: Function, revalidate?: boolean, fanoutTargets?: string[] }} [options] miss 拉取;`revalidate` 强制阻塞等待 fanout 择新
|
|
236
238
|
* @returns {Promise<Buffer | null>} 明文或 null
|
|
237
239
|
*/
|
|
238
240
|
export async function readPublicFile(replicaUsername, entityHash, logicalPath, options = {}) {
|
|
@@ -243,6 +245,7 @@ export async function readPublicFile(replicaUsername, entityHash, logicalPath, o
|
|
|
243
245
|
logicalPath,
|
|
244
246
|
cache: true,
|
|
245
247
|
revalidate: options.revalidate === true,
|
|
248
|
+
fanoutTargets: options.fanoutTargets,
|
|
246
249
|
})
|
|
247
250
|
if (!manifest) return null
|
|
248
251
|
return readManifestPlaintext(replicaUsername, manifest, options)
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto'
|
|
2
2
|
|
|
3
3
|
import { resolveNodeHash } from '../chunk/provider_registry.mjs'
|
|
4
|
-
import { fanoutFedFetch } from '../fetch_fanout.mjs'
|
|
4
|
+
import { canonicalizeFanoutTargets, fanoutFedFetch } from '../fetch_fanout.mjs'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* inflight + pending wait + fanout 共用骨架。
|
|
8
|
+
* 规范化目标集并拼入 inflight key:`fanoutTargets` 为数组(含空)即定向模式,undefined 为 node-scope public 模式。
|
|
8
9
|
* @template T
|
|
9
10
|
* @param {{
|
|
10
11
|
* inflight: { acquire: (key: string, start: () => { done: Promise<T | null>, cancel: () => void }) => Promise<T | null> | null },
|
|
11
|
-
*
|
|
12
|
+
* inflightKeyBase: string,
|
|
12
13
|
* username: string,
|
|
13
14
|
* action: string,
|
|
14
15
|
* registerWait: (requestId: string) => { done: Promise<T | null>, cancel: () => void },
|
|
@@ -19,20 +20,23 @@ import { fanoutFedFetch } from '../fetch_fanout.mjs'
|
|
|
19
20
|
*/
|
|
20
21
|
export function beginFedFanoutFetch({
|
|
21
22
|
inflight,
|
|
22
|
-
|
|
23
|
+
inflightKeyBase,
|
|
23
24
|
username,
|
|
24
25
|
action,
|
|
25
26
|
registerWait,
|
|
26
27
|
buildPayload,
|
|
27
28
|
fanoutTargets,
|
|
28
29
|
}) {
|
|
30
|
+
const targeted = Array.isArray(fanoutTargets)
|
|
31
|
+
const canonicalTargets = targeted ? canonicalizeFanoutTargets(fanoutTargets) : undefined
|
|
32
|
+
const inflightKey = `${inflightKeyBase}\0${targeted ? 'targeted' : 'public'}` + (canonicalTargets?.length ? `\0${canonicalTargets.join('\0')}` : '')
|
|
29
33
|
return inflight.acquire(inflightKey, () => {
|
|
30
34
|
const requestId = randomUUID()
|
|
31
35
|
const wait = registerWait(requestId)
|
|
32
36
|
void (async () => {
|
|
33
37
|
try {
|
|
34
38
|
const { nodeHash } = await resolveNodeHash(username)
|
|
35
|
-
await fanoutFedFetch(username, action, buildPayload(requestId, nodeHash),
|
|
39
|
+
await fanoutFedFetch(username, action, buildPayload(requestId, nodeHash), canonicalTargets)
|
|
36
40
|
}
|
|
37
41
|
catch { /* pending wait 超时/cancel 负责 settle */ }
|
|
38
42
|
})()
|
package/files/fetch_fanout.mjs
CHANGED
|
@@ -32,6 +32,7 @@ export function canonicalizeFanoutTargets(targets) {
|
|
|
32
32
|
/**
|
|
33
33
|
* 全局 miss 请求扇出:先向已知 peer 定向发送,再 trust-graph top-K fanout。
|
|
34
34
|
* 已直连 / follow hint peer 可能不在 trust-graph top-K(非成员 emoji CAS / Social 预览路径),故先定向发送。
|
|
35
|
+
* 拨号不阻塞请求窗口:已直连 peer 立即经现有链路投递,未直连 peer 先经群房间/overlay 尝试并后台拨号补发。
|
|
35
36
|
* @param {string} username 用户
|
|
36
37
|
* @param {string} action wire action 名
|
|
37
38
|
* @param {object} payload 请求载荷
|
|
@@ -52,8 +53,20 @@ export async function fanoutFedFetch(username, action, payload, fanoutTargets) {
|
|
|
52
53
|
|
|
53
54
|
const graph = await trustGraph.buildMergedGraph(username)
|
|
54
55
|
const peerTargets = fetchPeerTargets()
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
// 已直连 peer 立即经现有链路投递;其余 peer 后台并行:先经群房间/overlay 尝试投递,同时拨号,拨通且首投失败再补发。
|
|
57
|
+
// 全程不阻塞请求窗口(大 peer 池下 await 全量拨号会超窗口)。
|
|
58
|
+
const linked = new Set(listLinks().map(({ nodeHash }) => nodeHash))
|
|
59
|
+
for (const nodeHash of peerTargets) {
|
|
60
|
+
if (linked.has(nodeHash)) {
|
|
61
|
+
void trustGraph.sendToNode(username, nodeHash, action, payload, graph)
|
|
62
|
+
continue
|
|
63
|
+
}
|
|
64
|
+
const dialed = ensureLinkToNode(nodeHash).catch(() => null)
|
|
65
|
+
void trustGraph.sendToNode(username, nodeHash, action, payload, graph).then(async sent => {
|
|
66
|
+
if (sent) return
|
|
67
|
+
const link = await dialed
|
|
68
|
+
if (link) await trustGraph.sendToNode(username, nodeHash, action, payload, graph)
|
|
69
|
+
})
|
|
70
|
+
}
|
|
58
71
|
await trustGraph.fanoutToTopNodes(username, action, payload, FEDERATION_CHUNK_FETCH_FANOUT_K)
|
|
59
72
|
}
|
package/files/manifest/fetch.mjs
CHANGED
|
@@ -54,7 +54,7 @@ export async function fetchManifest(context) {
|
|
|
54
54
|
const localPromise = loadFileManifest(ownerEntityHash, logicalPath)
|
|
55
55
|
const shared = beginFedFanoutFetch({
|
|
56
56
|
inflight: manifestInflight,
|
|
57
|
-
|
|
57
|
+
inflightKeyBase: `${username}\0${expectedKey}`,
|
|
58
58
|
username,
|
|
59
59
|
action: 'fed_manifest_get',
|
|
60
60
|
/**
|